diff --git a/.gitignore b/.gitignore index e890337a2..38bd80d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ !/tmp/storage/.keep /data +*.sqlite3 +*.sqlite3_* + /public/assets # Ignore master key for decrypting credentials and more. diff --git a/app/models/application_record.rb b/app/models/application_record.rb index a043d384f..502f427b4 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,7 +1,12 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class - connects_to database: { writing: :primary, reading: :replica } + # SQLite doesn't use separate replica databases + if ENV.fetch("DATABASE_ADAPTER", "mysql") == "sqlite" + connects_to database: { writing: :primary, reading: :primary } + else + connects_to database: { writing: :primary, reading: :replica } + end attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } end diff --git a/config/database.yml b/config/database.yml index 2b5085410..9cd4820a5 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,4 +1,7 @@ <% + database_adapter = ENV.fetch("DATABASE_ADAPTER", "mysql") + use_sqlite = database_adapter == "sqlite" + if ENV["MIGRATE"].present? mysql_app_user_key = "MYSQL_ALTER_USER" mysql_app_password_key = "MYSQL_ALTER_PASSWORD" @@ -12,13 +15,24 @@ %> default: &default + <% if use_sqlite %> + adapter: sqlite3 + pool: 50 + timeout: 5000 + <% else %> adapter: trilogy host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> pool: 50 timeout: 5000 + <% end %> development: + <% if use_sqlite %> + primary: + <<: *default + database: db/development.sqlite3 + <% else %> primary: <<: *default database: fizzy_development @@ -26,28 +40,34 @@ development: replica: <<: *default database: fizzy_development - replica: true port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: true cable: <<: *default database: development_cable - migrations_paths: db/cable_migrate port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cable_migrate cache: <<: *default database: development_cache - migrations_paths: db/cache_migrate port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cache_migrate queue: <<: *default database: development_queue - migrations_paths: db/queue_migrate port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/queue_migrate + <% end %> # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: + <% if use_sqlite %> + primary: + <<: *default + database: db/test.sqlite3 + <% else %> primary: <<: *default database: fizzy_test @@ -57,6 +77,7 @@ test: database: fizzy_test port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> replica: true + <% end %> production: &production primary: diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index de51ae1f1..361d86075 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -6,7 +6,12 @@ end # Use DB read/write splitting for Active Storage models ActiveSupport.on_load(:active_storage_record) do - connects_to database: { writing: :primary, reading: :replica } + # SQLite doesn't use separate replica databases + if ENV.fetch("DATABASE_ADAPTER", "mysql") == "sqlite" + connects_to database: { writing: :primary, reading: :primary } + else + connects_to database: { writing: :primary, reading: :replica } + end end module ActiveStorageControllerExtensions diff --git a/config/initializers/sqlite_compatibility.rb b/config/initializers/sqlite_compatibility.rb new file mode 100644 index 000000000..3cff5522f --- /dev/null +++ b/config/initializers/sqlite_compatibility.rb @@ -0,0 +1,54 @@ +# SQLite compatibility layer - filters out MySQL-specific options when using SQLite + +# Define modules outside on_load so they're available immediately +module SQLiteCompatibility + module SQLiteTableDefinitionCompatibility + # Override column method to filter out MySQL-specific options + def column(name, type, **options) + # Check if we're using SQLite by checking the connection adapter + if @conn.is_a?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + # Remove MySQL-specific options that SQLite doesn't support + options = options.except(:size, :charset, :collation, :unsigned) + end + super(name, type, **options) + end + + # Override index method to filter out MySQL-specific index options + def index(column_name, **options) + if @conn.is_a?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + # Skip fulltext indexes entirely for SQLite + return if options[:type] == :fulltext + # SQLite doesn't support length-limited indexes + options = options.except(:length) + end + super(column_name, **options) + end + end + + module SQLiteSchemaStatementCompatibility + # Override create_table to filter out MySQL-specific table options + def create_table(table_name, **options) + if is_a?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + # Remove MySQL-specific table options + options = options.except(:charset, :collation) + end + super(table_name, **options) + end + end +end + +# Apply the prepends - both in on_load callback and immediately +def apply_sqlite_compatibility + if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + ActiveRecord::ConnectionAdapters::TableDefinition.prepend(SQLiteCompatibility::SQLiteTableDefinitionCompatibility) + ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(SQLiteCompatibility::SQLiteSchemaStatementCompatibility) + end +end + +# Run immediately if ActiveRecord is already loaded +apply_sqlite_compatibility + +# Also run when ActiveRecord loads (for cases where it loads later) +ActiveSupport.on_load(:active_record) do + apply_sqlite_compatibility +end diff --git a/config/initializers/uuid_primary_keys.rb b/config/initializers/uuid_primary_keys.rb index 560f5b384..a1a9dd8ec 100644 --- a/config/initializers/uuid_primary_keys.rb +++ b/config/initializers/uuid_primary_keys.rb @@ -16,7 +16,42 @@ ActiveSupport.on_load(:active_record) do end end - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlUuidAdapter) + if defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter) + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlUuidAdapter) + end + + module SqliteUuidAdapter + # Add UUID to SQLite's native database types + def native_database_types + @native_database_types_with_uuid ||= super.merge(uuid: { name: "blob", limit: 16 }) + end + + # Override lookup_cast_type to recognize BLOB as UUID type + def lookup_cast_type(sql_type) + if sql_type == "blob(16)" + ActiveRecord::Type.lookup(:uuid, adapter: :sqlite3) + else + super + end + end + + # Override fetch_type_metadata to preserve UUID type and limit + def fetch_type_metadata(sql_type) + if sql_type == "blob(16)" + ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new( + sql_type: sql_type, + type: :uuid, + limit: 16 + ) + else + super + end + end + end + + if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(SqliteUuidAdapter) + end module SchemaDumperUuidType # Map binary(16) columns to :uuid type in schema.rb @@ -29,7 +64,9 @@ ActiveSupport.on_load(:active_record) do end end - ActiveRecord::ConnectionAdapters::MySQL::SchemaDumper.prepend(SchemaDumperUuidType) + if defined?(ActiveRecord::ConnectionAdapters::MySQL::SchemaDumper) + ActiveRecord::ConnectionAdapters::MySQL::SchemaDumper.prepend(SchemaDumperUuidType) + end module TableDefinitionUuidSupport def uuid(name, **options) diff --git a/lib/rails_ext/active_record_uuid_type.rb b/lib/rails_ext/active_record_uuid_type.rb index 3aca09762..02bdf3ed3 100644 --- a/lib/rails_ext/active_record_uuid_type.rb +++ b/lib/rails_ext/active_record_uuid_type.rb @@ -39,5 +39,6 @@ module ActiveRecord end end -# Register the UUID type for Trilogy adapter +# Register the UUID type for Trilogy (MySQL) and SQLite3 adapters ActiveRecord::Type.register(:uuid, ActiveRecord::Type::Uuid, adapter: :trilogy) +ActiveRecord::Type.register(:uuid, ActiveRecord::Type::Uuid, adapter: :sqlite3) diff --git a/test/fixtures/sessions.yml b/test/fixtures/sessions.yml index a062f53fd..cf6abd120 100644 --- a/test/fixtures/sessions.yml +++ b/test/fixtures/sessions.yml @@ -7,5 +7,5 @@ kevin: jz: identity: jz -jason: - identity: jason +mike: + identity: mike