Selaa lähdekoodia

Initial commit

Billy Barrow 1 kuukausi sitten
sitoutus
501f970a59
5 muutettua tiedostoa jossa 206 lisäystä ja 0 poistoa
  1. 10 0
      meson.build
  2. 77 0
      src/DatabaseConfigurator.vala
  3. 49 0
      src/InversionTypeProvider.vala
  4. 31 0
      src/MigrationStartupService.vala
  5. 39 0
      src/meson.build

+ 10 - 0
meson.build

@@ -0,0 +1,10 @@
+project('invercargill-sql-inversion', ['vala', 'c'], version: '0.1')
+
+# Dependencies
+glib_dep = dependency('glib-2.0')
+gobject_dep = dependency('gobject-2.0')
+invercargill_dep = dependency('invercargill-1')
+inversion_dep = dependency('inversion-0.1')
+invercargill_sql_dep = dependency('invercargill-sql')
+
+subdir('src')

+ 77 - 0
src/DatabaseConfigurator.vala

@@ -0,0 +1,77 @@
+using Inversion;
+using InvercargillSql;
+using InvercargillSql.Dialects;
+using InvercargillSql.Migrations;
+using InvercargillSql.Orm;
+using InvercargillSql.Orm.Projections;
+
+namespace InvercargillSqlInversion {
+
+    public delegate void EntityMapperFactoryFunc<T>(EntityMapperBuilder<T> cfg);
+    public delegate void ProjectionFactoryFunc<T>(ProjectionBuilder<T> cfg) throws Error;
+
+    public class DatabaseConfigurator : Object {
+
+        private Container container = inject<Container>();
+        
+        public void register_connection(string connection_string, Lifecycle? lifecycle = null) throws Error {
+            var cs = ConnectionString.parse(connection_string);
+            
+            // Auto-detect lifecycle if not specified
+            var effective_lifecycle = lifecycle ?? detect_lifecycle(cs);
+
+            // Register the connection 
+            container.register<Connection>(() => ConnectionFactory.create_and_open(connection_string), effective_lifecycle);
+
+            // Register the dialect provider
+            container.register_transient<SqlDialect>(() => DialectFactory.create_from_string(connection_string));
+
+            // Register the migration runner
+            container.register_transient<MigrationRunner>(s => new MigrationRunner(s.resolve<Connection>(), s.resolve<SqlDialect>()));
+
+            // Register the type registry
+            container.register_singleton<InversionTypeProvider>()
+                .as<TypeProvider>();
+
+            // Register the orm session
+            container.register<OrmSession>(s => new OrmSession(s.resolve<Connection>(), s.resolve<TypeProvider>(), s.resolve<SqlDialect>()), effective_lifecycle);
+            
+        }
+        
+        public void add_migration<T>(TypedFactoryDelegate<T>? factory_func = null) {
+            container.register_transient<T>(factory_func)
+                .as<Migration>();
+        }
+
+        public void add_entity<T>(owned EntityMapperFactoryFunc<T> configuration_func) throws Error {
+            container.register_transient<EntityMapper>(s => {
+                var dialect = s.resolve<SqlDialect>();
+                var connection = s.resolve<Connection>();
+                var builder = new EntityMapperBuilder<T>();
+                configuration_func(builder);
+                var schema = dialect.introspect_schema(connection, builder.peek_table());
+                return builder.build_with_schema(schema);
+            });
+        }
+        
+        public void add_projection<T>(owned ProjectionFactoryFunc<T> configuration_func) throws Error {
+            container.register_transient<ProjectionDefinition>(s => {
+                var type_provider = s.resolve<TypeProvider>();
+                var builder = new ProjectionBuilder<T>(type_provider);
+                configuration_func(builder);
+                return builder.build();
+            });
+        }
+        
+        public void migrate_on_startup() {
+            container.register_startup<MigrationStartupService>();
+        }
+        
+
+        private static Lifecycle detect_lifecycle(ConnectionString cs) {
+            bool is_in_memory = cs.database == ":memory:" ||
+                               (cs.has_option("mode") && cs.get_option("mode") == "memory");
+            return is_in_memory ? Lifecycle.SINGLETON : Lifecycle.SCOPED;
+        }
+    }
+}

+ 49 - 0
src/InversionTypeProvider.vala

@@ -0,0 +1,49 @@
+using InvercargillSql.Orm;
+using Inversion;
+
+namespace InvercargillSqlInversion {
+
+    public class InversionTypeProvider : Object, TypeProvider {
+
+        private Scope scope = inject<Scope>();
+
+        public InvercargillSql.Orm.EntityMapper? get_mapper_for_type (GLib.Type type) throws Error {
+            var reg = scope.get_registrations(typeof(EntityMapper))
+                .where(r => r.get_metadata<MapperTypeInfo>().any(i => i.mapper_type == type))
+                .first_or_default();
+
+            if(reg == null) {
+                return null;
+            }
+            return (EntityMapper)scope.resolve_registration(reg);
+        }
+
+        public InvercargillSql.Orm.Projections.ProjectionDefinition? get_projection_for_type (GLib.Type type) throws Error {
+            var reg = scope.get_registrations(typeof(Projections.ProjectionDefinition))
+                .where(r => r.get_metadata<MapperTypeInfo>().any(i => i.mapper_type == type))
+                .first_or_default();
+
+            if(reg == null) {
+                return null;
+            }
+            return (Projections.ProjectionDefinition)scope.resolve_registration(reg);
+        }
+        public bool has_mapper_for_type (GLib.Type type) {
+            return scope.get_registrations(typeof(EntityMapper))
+                .any(r => r.get_metadata<MapperTypeInfo>().any(i => i.mapper_type == type));
+        }
+        public bool has_projection_for_type (GLib.Type type) {
+            return scope.get_registrations(typeof(Projections.ProjectionDefinition))
+                .any(r => r.get_metadata<MapperTypeInfo>().any(i => i.mapper_type == type));
+        }
+    }
+
+    public class MapperTypeInfo : Object {
+        public Type mapper_type { get; set; }
+
+        public MapperTypeInfo(Type type) {
+            mapper_type = type;
+        }
+    }
+
+}

+ 31 - 0
src/MigrationStartupService.vala

@@ -0,0 +1,31 @@
+using Inversion;
+using InvercargillSql.Migrations;
+
+namespace InvercargillSqlInversion {
+
+    /// <summary>
+    /// Startup service that runs database migrations on container initialization.
+    /// This service is registered with the STARTUP lifecycle and will be eagerly
+    /// instantiated when container.initialise() is called.
+    /// </summary>
+    public class MigrationStartupService : Object {
+        
+        /// <summary>
+        /// Creates a new MigrationStartupService and runs pending migrations.
+        /// </summary>
+        /// <param name="scope">The resolution scope used to resolve the MigrationRunner and migrations.</param>
+        /// <throws>Error">If migration fails or dependencies cannot be resolved.</throws>
+        public MigrationStartupService(Scope scope) throws Error {
+            var runner = scope.resolve<MigrationRunner>();
+            
+            // Get all registered migrations and register them with the runner
+            var migrations = scope.resolve_all<Migration>();
+            foreach (var migration in migrations) {
+                runner.register_migration(migration);
+            }
+            
+            // Execute migrations to the latest version
+            runner.migrate_to_latest();
+        }
+    }
+}

+ 39 - 0
src/meson.build

@@ -0,0 +1,39 @@
+sources = files(
+    'DatabaseConfigurator.vala',
+    'MigrationStartupService.vala',
+    'InversionTypeProvider.vala'
+)
+
+dependencies = [
+    glib_dep,
+    gobject_dep,
+    invercargill_dep,
+    inversion_dep,
+    invercargill_sql_dep
+]
+
+lib_sql_inversion = shared_library('invercargill-sql-inversion', sources,
+    dependencies: dependencies,
+    install: true,
+    vala_gir: 'invercargill_sql_inversion-0.1.gir',
+    install_dir: [true, true, true, true]
+)
+
+invercargill_sql_inversion_dep = declare_dependency(
+    link_with: lib_sql_inversion,
+    dependencies: dependencies
+)
+
+g_ir_compiler = find_program('g-ir-compiler')
+custom_target('invercargill-sql-inversion typelib', command: [g_ir_compiler, '--shared-library=libinvercargill-sql-inversion-0.1.so', '--output', '@OUTPUT@', meson.current_build_dir() / 'invercargill_sql_inversion-0.1.gir'],
+              output: 'invercargill-sql-inversion-0.1.typelib',
+              depends: lib_sql_inversion,
+              install: true,
+              install_dir: get_option('libdir') / 'girepository-1.0')
+
+# Generate pkg-config file
+pkg = import('pkgconfig')
+pkg.generate(lib_sql_inversion,
+    version: meson.project_version(),
+    name: 'invercargill-sql-inversion'
+)