rubydb 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -4
- data/adapters/activerecord/README.md +133 -9
- data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +18 -7
- data/adapters/activerecord/rubydb-activerecord.gemspec +12 -7
- data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +31 -9
- data/lib/rubydb/execution/plan.rb +9 -8
- data/lib/rubydb/execution/planner.rb +14 -10
- data/lib/rubydb/sql/ast/insert.rb +27 -16
- data/lib/rubydb/sql/parser.rb +27 -17
- data/lib/rubydb/storage/engine.rb +24 -8
- data/lib/rubydb/version.rb +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5cfc85dc4d3cfca89bd7d89335e4bf887fbfca212836f8a62d92a70a63f0f284
|
|
4
|
+
data.tar.gz: 9869f03b9da1a7e0cfd0c01f1f22ae9b5ba0e2b12eedaeac72e1b4deab878a0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76650c2403e26244619687eb6146bb7a1561d3764d664094a6deb94e02566924c60979f481aca972be026987775f8c9b68ed02804d15e583f885528e02833613
|
|
7
|
+
data.tar.gz: 60c894fde163c1a47ed84aa2c2d8b374a9b6c70f3d810feb7ca419a06aa2cb5e6026d8aa278d27ed89ad4a83b48405c31137671f1d2dab7240d0efc36b367de8
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
All notable changes to RubyDB are documented here. Versions follow
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
-
## Unreleased
|
|
6
|
+
## Unreleased
|
|
7
7
|
|
|
8
8
|
- Added full developer, troubleshooting, debugging, production operations,
|
|
9
9
|
Rails compatibility, and SQL compatibility guides with repository maps,
|
|
@@ -21,9 +21,25 @@ All notable changes to RubyDB are documented here. Versions follow
|
|
|
21
21
|
- Added a full CLI guide and cheat sheet covering lifecycle, server operation,
|
|
22
22
|
shell, migrations, backup/restore, snapshots, branches, inspection, doctor,
|
|
23
23
|
vacuum, maintenance, and release workflows.
|
|
24
|
-
- Clarified the tested common SQLite-style profile and production limits.
|
|
25
|
-
|
|
26
|
-
## 0.1.
|
|
24
|
+
- Clarified the tested common SQLite-style profile and production limits.
|
|
25
|
+
|
|
26
|
+
## 0.1.2 - 2026-09-09
|
|
27
|
+
|
|
28
|
+
- Added SQLite/ActiveRecord-compatible `INSERT ... DEFAULT VALUES` parsing,
|
|
29
|
+
planning, execution, default materialization, and regression coverage.
|
|
30
|
+
- Normalized literal schema defaults before they reach physical rows so empty
|
|
31
|
+
strings and other scalar defaults are not persisted as AST wrapper objects.
|
|
32
|
+
- Released `rubydb-activerecord` 0.1.1 with scalar default unwrapping for
|
|
33
|
+
ActiveRecord column metadata.
|
|
34
|
+
|
|
35
|
+
## 0.1.1 - 2026-09-09
|
|
36
|
+
|
|
37
|
+
- Published the Rails connection configuration fixes for embedded database
|
|
38
|
+
paths and `rubydb://`/`rubydbs://` connection URLs.
|
|
39
|
+
- Kept the patch release compatible with the RubyDB 0.1.x adapter dependency
|
|
40
|
+
range.
|
|
41
|
+
|
|
42
|
+
## 0.1.0 - 2026-09-07
|
|
27
43
|
|
|
28
44
|
- Initial public release candidate.
|
|
29
45
|
- Includes the embedded engine, ActiveRecord adapter, backup/recovery, WAL,
|
|
@@ -1,9 +1,133 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
# rubydb-activerecord
|
|
2
|
+
|
|
3
|
+
`rubydb-activerecord` is the ActiveRecord adapter for [RubyDB](https://github.com/aldanedev-create/rubydb). It lets Rails applications use RubyDB through the normal model, relation, transaction, migration, schema, and connection-pool APIs.
|
|
4
|
+
|
|
5
|
+
This gem is an adapter to RubyDB's SQL engine. It is not a PostgreSQL, MySQL, or SQLite wire-protocol compatibility layer, and it does not make arbitrary dialect-specific SQL portable. Validate your application's queries and migrations against the exact Ruby, Rails, RubyDB, operating-system, and deployment versions you will run.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Ruby 3.3 or newer
|
|
10
|
+
- RubyDB 0.1.x
|
|
11
|
+
- ActiveRecord 7.1, 7.2, or 8.0 (the dependency range is `>= 7.1`, `< 8.1`)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Add both gems to the Rails application's `Gemfile`:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "rubydb"
|
|
19
|
+
gem "rubydb-activerecord"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then run:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The adapter registers itself under the `rubydb` adapter name when it is
|
|
29
|
+
required by Bundler. For a manually loaded application, require it explicitly:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require "rubydb"
|
|
33
|
+
require "active_record/connection_adapters/rubydb_adapter"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Development: embedded database
|
|
37
|
+
|
|
38
|
+
Embedded mode is convenient for local development and a controlled
|
|
39
|
+
single-process application. The process owns the database file:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
# config/database.yml
|
|
43
|
+
development:
|
|
44
|
+
adapter: rubydb
|
|
45
|
+
embedded: true
|
|
46
|
+
database: <%= Rails.root.join("tmp/development.rdb") %>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Run migrations normally:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bin/rails db:migrate
|
|
53
|
+
bin/rails console
|
|
54
|
+
bin/rails server
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Do not open the same embedded path from multiple independent Rails processes.
|
|
58
|
+
For multiple web workers, job workers, or hosts, use a managed RubyDB server.
|
|
59
|
+
|
|
60
|
+
## Production: managed server
|
|
61
|
+
|
|
62
|
+
Use a network connection when the application has more than one process or
|
|
63
|
+
when the database must be operated independently from the application. Keep
|
|
64
|
+
credentials in the platform secret manager and map them into `database.yml`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# config/database.yml
|
|
68
|
+
production:
|
|
69
|
+
adapter: rubydb
|
|
70
|
+
embedded: false
|
|
71
|
+
url: <%= ENV.fetch("RUBYDB_URL") %>
|
|
72
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
73
|
+
timeout: <%= ENV.fetch("RUBYDB_TIMEOUT", "30") %>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `rubydb://` for a plain private-network connection or `rubydbs://` for TLS:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
rubydbs://app_user:URL_ENCODED_PASSWORD@db.internal.example:7432/app?verify_peer=true&ca_file=%2Fetc%2Frubydb%2Ftls%2Fca.crt
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The server must be deployed separately with persistent storage,
|
|
83
|
+
authentication, TLS, backups, monitoring, resource limits, and a tested
|
|
84
|
+
restore procedure. Size its connection limit for the total Rails pool across
|
|
85
|
+
all application processes, with headroom for administration and replication.
|
|
86
|
+
See the repository's [Rails database configuration](../../docs/rails/database-yml.md)
|
|
87
|
+
and [production guidance](../../docs/rails/production.md).
|
|
88
|
+
|
|
89
|
+
## Adapter surface
|
|
90
|
+
|
|
91
|
+
The release test suite exercises:
|
|
92
|
+
|
|
93
|
+
- ActiveRecord CRUD, binds, quoted identifiers, type casting, and false/zero values
|
|
94
|
+
- transactions and savepoints
|
|
95
|
+
- `joins`, qualified filters, ordering, eager loading, and nested associations
|
|
96
|
+
- table/column/index/foreign-key introspection
|
|
97
|
+
- Rails migrations, defaults, indexes, schema dumps, and populated-table changes
|
|
98
|
+
- embedded and network connection setup paths
|
|
99
|
+
|
|
100
|
+
RubyDB currently has documented SQL and schema boundaries. Features outside
|
|
101
|
+
the tested surface—such as dialect-specific extensions, generated columns,
|
|
102
|
+
complex table rebuilds, or application-specific Arel—need explicit tests
|
|
103
|
+
before deployment.
|
|
104
|
+
|
|
105
|
+
## Validation
|
|
106
|
+
|
|
107
|
+
Run the adapter's integration suite from this directory:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
bundle install
|
|
111
|
+
bundle exec rspec spec
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Run the repository's Rails-focused checks from the repository root:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
bundle exec rspec spec/rails_adapter_live_engine_spec.rb spec/rails_adapter_schema_dump_spec.rb spec/rails_schema_statements_spec.rb
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Before a production release, run a real migration and smoke query through the
|
|
121
|
+
same server URL, TLS settings, pool size, and secret-management path used by
|
|
122
|
+
the deployment. Keep a verified backup before migrations and rehearse restore
|
|
123
|
+
and rollback on a representative copy.
|
|
124
|
+
|
|
125
|
+
## Support and release policy
|
|
126
|
+
|
|
127
|
+
The adapter version is coupled to the RubyDB 0.1.x release line. Pin both gems
|
|
128
|
+
in the application lockfile, upgrade them together, and run the adapter suite
|
|
129
|
+
before upgrading Rails. Report reproducible adapter or engine issues at the
|
|
130
|
+
project's [issue tracker](https://github.com/aldanedev-create/rubydb/issues)
|
|
131
|
+
with Ruby/Rails/RubyDB versions, the SQL or migration involved, and a redacted
|
|
132
|
+
configuration summary. Never include passwords, connection URLs, database
|
|
133
|
+
files, or private keys in reports.
|
|
@@ -756,10 +756,15 @@ module ActiveRecord
|
|
|
756
756
|
|
|
757
757
|
# ActiveRecord's generic Column deduplication is string-oriented. RubyDB
|
|
758
758
|
# persists typed defaults, so serialize scalar defaults at this boundary
|
|
759
|
-
# and let ActiveRecord cast them through the column type map.
|
|
760
|
-
def rails_default_value(value)
|
|
761
|
-
|
|
762
|
-
|
|
759
|
+
# and let ActiveRecord cast them through the column type map.
|
|
760
|
+
def rails_default_value(value)
|
|
761
|
+
# RubyDB exposes SQL defaults as AST literals. ActiveRecord expects
|
|
762
|
+
# the scalar payload when it builds its Column metadata; calling
|
|
763
|
+
# `to_s` on the wrapper would leak the Ruby object inspection into
|
|
764
|
+
# newly instantiated records.
|
|
765
|
+
value = value.value if value.respond_to?(:value) && !value.is_a?(String)
|
|
766
|
+
value.is_a?(String) ? value : value.to_s
|
|
767
|
+
end
|
|
763
768
|
|
|
764
769
|
def sql_for_execution(sql)
|
|
765
770
|
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
|
@@ -775,9 +780,15 @@ module ActiveRecord
|
|
|
775
780
|
column.is_a?(Hash) ? (column[:name] || column["name"] || column) : column
|
|
776
781
|
end.map(&:to_s)
|
|
777
782
|
end
|
|
778
|
-
values = rows.map do |row|
|
|
779
|
-
columns.map
|
|
780
|
-
|
|
783
|
+
values = rows.map do |row|
|
|
784
|
+
columns.map do |column|
|
|
785
|
+
if row.respond_to?(:key?) && row.key?(column)
|
|
786
|
+
row[column]
|
|
787
|
+
elsif row.respond_to?(:key?) && row.key?(column.to_sym)
|
|
788
|
+
row[column.to_sym]
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
end
|
|
781
792
|
ActiveRecord::Result.new(columns, values)
|
|
782
793
|
end
|
|
783
794
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |spec|
|
|
4
4
|
spec.name = "rubydb-activerecord"
|
|
5
|
-
spec.version = "0.1.
|
|
5
|
+
spec.version = "0.1.1"
|
|
6
6
|
spec.authors = ["Aldane Hutchinson"]
|
|
7
7
|
spec.email = ["aldanehutchinson5@gmail.com"]
|
|
8
8
|
|
|
@@ -10,12 +10,17 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.description = "ActiveRecord adapter for the RubyDB database"
|
|
11
11
|
spec.homepage = "https://github.com/aldanedev-create/rubydb"
|
|
12
12
|
spec.license = "MIT"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
spec.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
spec.
|
|
13
|
+
# rubydb itself requires Ruby 3.3 or newer. Keep the adapter's runtime
|
|
14
|
+
# contract aligned so Bundler cannot select an unsupported combination.
|
|
15
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "#{spec.homepage}/tree/main/adapters/activerecord"
|
|
19
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/aldanedev-create/rubydb/issues"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/aldanedev-create/rubydb/blob/main/CHANGELOG.md"
|
|
21
|
+
spec.metadata["documentation_uri"] = "https://github.com/aldanedev-create/rubydb/tree/main/docs"
|
|
22
|
+
|
|
23
|
+
spec.files = Dir.glob("lib/**/*.rb") + %w[README.md rubydb-activerecord.gemspec]
|
|
19
24
|
spec.require_paths = ["lib"]
|
|
20
25
|
|
|
21
26
|
spec.add_dependency "activerecord", ">= 7.1", "< 8.1"
|
|
@@ -31,17 +31,27 @@ RSpec.describe ActiveRecord::ConnectionAdapters::RubyDBAdapter do
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
it "creates and finds an ActiveRecord model through the embedded engine" do
|
|
34
|
+
it "creates and finds an ActiveRecord model through the embedded engine" do
|
|
35
35
|
connection = ActiveRecord::Base.connection
|
|
36
36
|
connection.execute("CREATE TABLE accounts (id INTEGER PRIMARY KEY, email VARCHAR(255) NOT NULL, active BOOLEAN NOT NULL)")
|
|
37
37
|
|
|
38
38
|
created = model.create!(id: 1, email: "ada@example.test", active: true)
|
|
39
39
|
loaded = model.find(created.id)
|
|
40
40
|
|
|
41
|
-
expect(loaded.attributes).to include("id" => 1, "email" => "ada@example.test", "active" => true)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it "
|
|
41
|
+
expect(loaded.attributes).to include("id" => 1, "email" => "ada@example.test", "active" => true)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "preserves false and zero values in ActiveRecord results" do
|
|
45
|
+
connection = ActiveRecord::Base.connection
|
|
46
|
+
connection.execute("CREATE TABLE flags (id INTEGER PRIMARY KEY, enabled BOOLEAN NOT NULL, attempts INTEGER NOT NULL)")
|
|
47
|
+
connection.execute("INSERT INTO flags (id, enabled, attempts) VALUES (1, FALSE, 0)")
|
|
48
|
+
|
|
49
|
+
result = connection.exec_query("SELECT enabled, attempts FROM flags WHERE id = 1")
|
|
50
|
+
|
|
51
|
+
expect(result.first).to include("enabled" => false, "attempts" => 0)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "runs a Rails migration that creates a table, adds a column, and adds an index" do
|
|
45
55
|
migration = Class.new(ActiveRecord::Migration[migration_version]) do
|
|
46
56
|
def change
|
|
47
57
|
create_table :projects do |table|
|
|
@@ -64,10 +74,22 @@ RSpec.describe ActiveRecord::ConnectionAdapters::RubyDBAdapter do
|
|
|
64
74
|
expect(schema).not_to include('t.integer "id"')
|
|
65
75
|
|
|
66
76
|
migration.new.migrate(:down)
|
|
67
|
-
expect(connection.table_exists?(:projects)).to be(false)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it "
|
|
77
|
+
expect(connection.table_exists?(:projects)).to be(false)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "exposes scalar schema defaults to ActiveRecord" do
|
|
81
|
+
connection = ActiveRecord::Base.connection
|
|
82
|
+
connection.execute("CREATE TABLE settings (id INTEGER PRIMARY KEY, label VARCHAR(255) NOT NULL DEFAULT '')")
|
|
83
|
+
|
|
84
|
+
settings_model = Class.new(ActiveRecord::Base) do
|
|
85
|
+
self.table_name = "settings"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
expect(settings_model.new.label).to eq("")
|
|
89
|
+
expect(settings_model.create!.reload.label).to eq("")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "executes an ActiveRecord association join with qualified filtering" do
|
|
71
93
|
stub_const("RubydbAccount", Class.new(ActiveRecord::Base) do
|
|
72
94
|
self.table_name = "accounts"
|
|
73
95
|
has_many :rubydb_projects, class_name: "RubydbProject", foreign_key: :account_id
|
|
@@ -124,14 +124,15 @@ module RubyDB
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
class Insert < Plan
|
|
127
|
-
attr_reader :values, :rows, :on_conflict
|
|
128
|
-
|
|
129
|
-
def initialize(table_name, columns = [], values = [], rows: nil, on_conflict: nil)
|
|
130
|
-
super(:insert, table_name, columns)
|
|
131
|
-
@rows = rows || [values]
|
|
132
|
-
@values = @rows.first || []
|
|
133
|
-
@on_conflict = on_conflict
|
|
134
|
-
|
|
127
|
+
attr_reader :values, :rows, :on_conflict, :default_values
|
|
128
|
+
|
|
129
|
+
def initialize(table_name, columns = [], values = [], rows: nil, on_conflict: nil, default_values: false)
|
|
130
|
+
super(:insert, table_name, columns)
|
|
131
|
+
@rows = rows || [values]
|
|
132
|
+
@values = @rows.first || []
|
|
133
|
+
@on_conflict = on_conflict
|
|
134
|
+
@default_values = default_values
|
|
135
|
+
end
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
class Update < Plan
|
|
@@ -154,12 +154,13 @@ module RubyDB
|
|
|
154
154
|
|
|
155
155
|
def plan_insert(statement)
|
|
156
156
|
Plan::Insert.new(
|
|
157
|
-
statement.table,
|
|
158
|
-
statement.columns,
|
|
159
|
-
statement.values,
|
|
160
|
-
rows: statement.rows,
|
|
161
|
-
on_conflict: statement.on_conflict
|
|
162
|
-
|
|
157
|
+
statement.table,
|
|
158
|
+
statement.columns,
|
|
159
|
+
statement.values,
|
|
160
|
+
rows: statement.rows,
|
|
161
|
+
on_conflict: statement.on_conflict,
|
|
162
|
+
default_values: statement.default_values?
|
|
163
|
+
)
|
|
163
164
|
end
|
|
164
165
|
|
|
165
166
|
def plan_update(statement)
|
|
@@ -186,10 +187,13 @@ module RubyDB
|
|
|
186
187
|
plan
|
|
187
188
|
end
|
|
188
189
|
|
|
189
|
-
def plan_create_table(statement)
|
|
190
|
-
columns = statement.columns.map do |col|
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
def plan_create_table(statement)
|
|
191
|
+
columns = statement.columns.map do |col|
|
|
192
|
+
options = col.options.dup
|
|
193
|
+
default = options[:default]
|
|
194
|
+
options[:default] = default.value if default.respond_to?(:value) && !default.is_a?(String)
|
|
195
|
+
Catalog::Column.new(col.name, col.type_class, **options)
|
|
196
|
+
end
|
|
193
197
|
|
|
194
198
|
Plan::CreateTable.new(
|
|
195
199
|
statement.name,
|
|
@@ -5,15 +5,17 @@ module RubyDB
|
|
|
5
5
|
module AST
|
|
6
6
|
# INSERT statement AST node
|
|
7
7
|
class Insert < Node
|
|
8
|
-
attr_reader :table, :columns, :values, :rows, :on_conflict
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@
|
|
14
|
-
@
|
|
15
|
-
@
|
|
16
|
-
@
|
|
8
|
+
attr_reader :table, :columns, :values, :rows, :on_conflict
|
|
9
|
+
attr_reader :default_values
|
|
10
|
+
|
|
11
|
+
def initialize(table, columns = [], values = [], rows: nil, on_conflict: nil, default_values: false, location: nil)
|
|
12
|
+
super(location: location)
|
|
13
|
+
@table = table
|
|
14
|
+
@columns = columns
|
|
15
|
+
@rows = rows || [values]
|
|
16
|
+
@values = @rows.first || []
|
|
17
|
+
@on_conflict = on_conflict
|
|
18
|
+
@default_values = default_values
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def accept(visitor)
|
|
@@ -24,8 +26,9 @@ module RubyDB
|
|
|
24
26
|
Insert.new(
|
|
25
27
|
@table,
|
|
26
28
|
@columns.dup,
|
|
27
|
-
@values.map(&:clone), rows: @rows.map { |row| row.map(&:clone) }, on_conflict: @on_conflict,
|
|
28
|
-
|
|
29
|
+
@values.map(&:clone), rows: @rows.map { |row| row.map(&:clone) }, on_conflict: @on_conflict,
|
|
30
|
+
default_values: @default_values,
|
|
31
|
+
location: @location
|
|
29
32
|
)
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -37,8 +40,12 @@ module RubyDB
|
|
|
37
40
|
parts << "(#{@columns.join(", ")})"
|
|
38
41
|
end
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
if @default_values
|
|
44
|
+
parts << "DEFAULT VALUES"
|
|
45
|
+
else
|
|
46
|
+
parts << "VALUES"
|
|
47
|
+
parts << @rows.map { |row| "(#{row.map(&:to_sql).join(", ")})" }.join(", ")
|
|
48
|
+
end
|
|
42
49
|
parts << "ON CONFLICT DO NOTHING" if @on_conflict == :nothing
|
|
43
50
|
if @on_conflict.is_a?(Hash) && @on_conflict[:action] == :update
|
|
44
51
|
target = @on_conflict[:target].any? ? " (#{@on_conflict[:target].join(', ')})" : ""
|
|
@@ -56,9 +63,13 @@ module RubyDB
|
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
# Helper methods
|
|
59
|
-
def has_columns?
|
|
60
|
-
@columns.any?
|
|
61
|
-
end
|
|
66
|
+
def has_columns?
|
|
67
|
+
@columns.any?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def default_values?
|
|
71
|
+
@default_values
|
|
72
|
+
end
|
|
62
73
|
|
|
63
74
|
def value_count
|
|
64
75
|
@values.size
|
data/lib/rubydb/sql/parser.rb
CHANGED
|
@@ -539,21 +539,30 @@ module RubyDB
|
|
|
539
539
|
expect(Token::Type::RPAREN)
|
|
540
540
|
end
|
|
541
541
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
expect(Token::Type::
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
542
|
+
default_values = false
|
|
543
|
+
if current_token&.type == Token::Type::DEFAULT
|
|
544
|
+
advance
|
|
545
|
+
expect(Token::Type::VALUES)
|
|
546
|
+
default_values = true
|
|
547
|
+
else
|
|
548
|
+
expect(Token::Type::VALUES)
|
|
549
|
+
end
|
|
550
|
+
rows = default_values ? [[]] : []
|
|
551
|
+
unless default_values
|
|
552
|
+
loop do
|
|
553
|
+
expect(Token::Type::LPAREN)
|
|
554
|
+
values = []
|
|
555
|
+
while true
|
|
556
|
+
values << parse_expression
|
|
557
|
+
break unless current_token&.type == Token::Type::COMMA
|
|
558
|
+
advance
|
|
559
|
+
end
|
|
560
|
+
expect(Token::Type::RPAREN)
|
|
561
|
+
rows << values
|
|
562
|
+
break unless current_token&.type == Token::Type::COMMA
|
|
563
|
+
advance
|
|
564
|
+
end
|
|
565
|
+
end
|
|
557
566
|
|
|
558
567
|
on_conflict = nil
|
|
559
568
|
if current_token&.type == Token::Type::ON
|
|
@@ -589,8 +598,9 @@ module RubyDB
|
|
|
589
598
|
raise ParserError, "Expected NOTHING or UPDATE after ON CONFLICT DO"
|
|
590
599
|
end
|
|
591
600
|
end
|
|
592
|
-
AST::Insert.new(table, columns, rows.first || [], rows: rows, on_conflict: on_conflict
|
|
593
|
-
|
|
601
|
+
AST::Insert.new(table, columns, rows.first || [], rows: rows, on_conflict: on_conflict,
|
|
602
|
+
default_values: default_values)
|
|
603
|
+
end
|
|
594
604
|
|
|
595
605
|
def parse_update
|
|
596
606
|
expect(Token::Type::UPDATE)
|
|
@@ -675,16 +675,32 @@ module RubyDB
|
|
|
675
675
|
end
|
|
676
676
|
|
|
677
677
|
# Row operations
|
|
678
|
-
def insert_row(table_name, columns, values)
|
|
679
|
-
ensure_writable!
|
|
680
|
-
table_name = resolve_table_name(table_name)
|
|
681
|
-
@lock.synchronize do
|
|
678
|
+
def insert_row(table_name, columns, values)
|
|
679
|
+
ensure_writable!
|
|
680
|
+
table_name = resolve_table_name(table_name)
|
|
681
|
+
@lock.synchronize do
|
|
682
682
|
@stats[:row_inserts] += 1
|
|
683
683
|
|
|
684
|
-
metadata = @table_metadata[table_name]
|
|
685
|
-
raise DatabaseError, "Table '#{table_name}' does not exist" unless metadata
|
|
686
|
-
|
|
687
|
-
#
|
|
684
|
+
metadata = @table_metadata[table_name]
|
|
685
|
+
raise DatabaseError, "Table '#{table_name}' does not exist" unless metadata
|
|
686
|
+
|
|
687
|
+
# SQL INSERT may omit columns entirely (DEFAULT VALUES) or omit
|
|
688
|
+
# only selected columns. Materialize declared defaults before
|
|
689
|
+
# constraint validation so NOT NULL defaults are valid and the
|
|
690
|
+
# physical row contains scalar values rather than AST wrappers.
|
|
691
|
+
if values.is_a?(Hash)
|
|
692
|
+
values = values.dup
|
|
693
|
+
columns.each do |column|
|
|
694
|
+
present = values.key?(column.name) || values.key?(column.name.to_sym)
|
|
695
|
+
next if present || !column.has_default?
|
|
696
|
+
|
|
697
|
+
default = column.default
|
|
698
|
+
default = default.value if default.respond_to?(:value)
|
|
699
|
+
values[column.name] = default
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Rails and other SQL clients omit an INTEGER primary key on insert
|
|
688
704
|
# and expect the database to allocate it. Keep allocation in the
|
|
689
705
|
# engine so embedded and server connections have identical behavior.
|
|
690
706
|
if values.is_a?(Hash)
|
data/lib/rubydb/version.rb
CHANGED
|
@@ -11,13 +11,13 @@ module RubyDB
|
|
|
11
11
|
# - MAJOR: Incompatible API changes
|
|
12
12
|
# - MINOR: Backwards-compatible new functionality
|
|
13
13
|
# - PATCH: Backwards-compatible bug fixes
|
|
14
|
-
VERSION = "0.1.
|
|
14
|
+
VERSION = "0.1.2"
|
|
15
15
|
|
|
16
16
|
# Version components for easy access
|
|
17
17
|
module Version
|
|
18
18
|
MAJOR = 0
|
|
19
19
|
MINOR = 1
|
|
20
|
-
PATCH =
|
|
20
|
+
PATCH = 2
|
|
21
21
|
PRE = nil # e.g., "alpha", "beta", "rc1"
|
|
22
22
|
|
|
23
23
|
def self.to_s
|
|
@@ -30,4 +30,4 @@ module RubyDB
|
|
|
30
30
|
version
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
-
end
|
|
33
|
+
end
|