activerecord-duckdb 0.1.0
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 +7 -0
- data/.fasterer.yml +31 -0
- data/.rspec +3 -0
- data/.rubocop.yml +105 -0
- data/.simplecov +18 -0
- data/.tool-versions +1 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +131 -0
- data/LICENSE.txt +21 -0
- data/README.md +168 -0
- data/Rakefile +19 -0
- data/TODO.md +16 -0
- data/docs/TABLE_DEFINITION.md +322 -0
- data/lib/active_record/connection_adapters/duckdb/column.rb +67 -0
- data/lib/active_record/connection_adapters/duckdb/database_limits.rb +54 -0
- data/lib/active_record/connection_adapters/duckdb/database_statements.rb +303 -0
- data/lib/active_record/connection_adapters/duckdb/database_tasks.rb +22 -0
- data/lib/active_record/connection_adapters/duckdb/quoting.rb +59 -0
- data/lib/active_record/connection_adapters/duckdb/schema_creation.rb +43 -0
- data/lib/active_record/connection_adapters/duckdb/schema_definitions.rb +145 -0
- data/lib/active_record/connection_adapters/duckdb/schema_dumper.rb +165 -0
- data/lib/active_record/connection_adapters/duckdb/schema_statements.rb +599 -0
- data/lib/active_record/connection_adapters/duckdb_adapter.rb +436 -0
- data/lib/active_record/tasks/duckdb_database_tasks.rb +170 -0
- data/lib/activerecord/duckdb/version.rb +11 -0
- data/lib/activerecord-duckdb.rb +60 -0
- data/sig/activerecord/duckdb.rbs +6 -0
- data/tmp/.gitkeep +0 -0
- metadata +107 -0
@@ -0,0 +1,322 @@
|
|
1
|
+
# DuckDB TableDefinition Guide
|
2
|
+
|
3
|
+
This guide covers the `TableDefinition` class for the ActiveRecord DuckDB adapter, which provides full Rails compatibility plus support for DuckDB-specific data types.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The DuckDB adapter's `TableDefinition` follows standard Rails conventions while adding:
|
8
|
+
|
9
|
+
- **Rails Compatibility**: All standard Rails table creation patterns work seamlessly
|
10
|
+
- **Flexible Primary Keys**: Support for integer, bigint, UUID, and string primary keys using Rails conventions
|
11
|
+
- **DuckDB Data Types**: Support for DuckDB-specific types like `HUGEINT`, `UUID`, `LIST`, `STRUCT`, etc.
|
12
|
+
- **Automatic Sequences**: Behind-the-scenes sequence management for integer primary keys
|
13
|
+
</text>
|
14
|
+
|
15
|
+
<old_text>
|
16
|
+
|
17
|
+
### Integer Primary Keys (with Sequences)
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# Standard Rails table - automatically creates bigint id with sequence
|
21
|
+
create_table :users do |t|
|
22
|
+
t.string :name
|
23
|
+
t.timestamps
|
24
|
+
end
|
25
|
+
# DuckDB adapter automatically creates users_id_seq sequence
|
26
|
+
|
27
|
+
# Explicit integer primary key (Rails 4 style)
|
28
|
+
create_table :posts, id: :integer do |t|
|
29
|
+
t.string :title
|
30
|
+
t.timestamps
|
31
|
+
end
|
32
|
+
# Creates posts_id_seq sequence with integer type
|
33
|
+
|
34
|
+
# Explicit bigint primary key (Rails 5+ default)
|
35
|
+
create_table :articles, id: :bigint do |t|
|
36
|
+
t.string :title
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
# Creates articles_id_seq sequence
|
40
|
+
```
|
41
|
+
|
42
|
+
## Primary Key Support
|
43
|
+
|
44
|
+
### Integer Primary Keys (with Sequences)
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# Standard Rails table - automatically creates bigint id with sequence
|
48
|
+
create_table :users do |t|
|
49
|
+
t.string :name
|
50
|
+
t.timestamps
|
51
|
+
end
|
52
|
+
# DuckDB adapter automatically creates users_id_seq sequence
|
53
|
+
|
54
|
+
# Explicit integer primary key (Rails 4 style)
|
55
|
+
create_table :posts, id: :integer do |t|
|
56
|
+
t.string :title
|
57
|
+
t.timestamps
|
58
|
+
end
|
59
|
+
# Creates posts_id_seq sequence with integer type
|
60
|
+
|
61
|
+
# Explicit bigint primary key (Rails 5+ default)
|
62
|
+
create_table :articles, id: :bigint do |t|
|
63
|
+
t.string :title
|
64
|
+
t.timestamps
|
65
|
+
end
|
66
|
+
# Creates articles_id_seq sequence
|
67
|
+
```
|
68
|
+
|
69
|
+
### UUID Primary Keys (no sequences needed)
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# Rails convention for UUID primary keys
|
73
|
+
create_table :sessions, id: :uuid do |t|
|
74
|
+
t.string :session_token
|
75
|
+
t.timestamps
|
76
|
+
end
|
77
|
+
# No sequence created - UUIDs are generated by the application
|
78
|
+
|
79
|
+
# UUID with custom primary key name
|
80
|
+
create_table :articles, primary_key: :article_uuid, id: :uuid do |t|
|
81
|
+
t.string :title
|
82
|
+
t.timestamps
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### String Primary Keys
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# Rails convention for string primary keys
|
90
|
+
create_table :categories, id: :string do |t|
|
91
|
+
t.string :name
|
92
|
+
end
|
93
|
+
# No sequence created - string IDs managed by application
|
94
|
+
|
95
|
+
# Custom string primary key name
|
96
|
+
create_table :countries, primary_key: :iso_code, id: :string do |t|
|
97
|
+
t.string :name
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
## Sequence Management
|
102
|
+
|
103
|
+
### Advanced Sequence Management
|
104
|
+
|
105
|
+
If you need custom sequence behavior, you can use the sequence methods directly:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# Create a custom sequence
|
109
|
+
connection.create_sequence('custom_seq', start_with: 1000, increment_by: 10)
|
110
|
+
|
111
|
+
# Use in a table (manual approach)
|
112
|
+
create_table :invoices do |t|
|
113
|
+
t.bigint :invoice_number, default: -> { "nextval('custom_seq')" }
|
114
|
+
t.decimal :amount
|
115
|
+
t.timestamps
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
## DuckDB-Specific Column Types
|
120
|
+
|
121
|
+
### Integer Variants
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
create_table :analytics do |t|
|
125
|
+
t.tinyint :small_counter # 1 byte: -128 to 127
|
126
|
+
t.smallint :medium_counter # 2 bytes: -32,768 to 32,767
|
127
|
+
t.integer :standard_counter # 4 bytes (standard)
|
128
|
+
t.bigint :large_counter # 8 bytes
|
129
|
+
t.hugeint :massive_counter # 16 bytes: very large integers
|
130
|
+
|
131
|
+
# Unsigned variants
|
132
|
+
t.utinyint :positive_small # 1 byte: 0 to 255
|
133
|
+
t.usmallint :positive_medium # 2 bytes: 0 to 65,535
|
134
|
+
t.uinteger :positive_standard # 4 bytes: 0 to 4,294,967,295
|
135
|
+
t.ubigint :positive_large # 8 bytes
|
136
|
+
t.uhugeint :positive_massive # 16 bytes
|
137
|
+
|
138
|
+
t.varint :flexible_precision # Variable precision (up to 1.2M digits)
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
### Special Data Types
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
create_table :advanced_types do |t|
|
146
|
+
t.uuid :identifier # UUID type
|
147
|
+
t.interval :duration # Time intervals
|
148
|
+
t.bit :flags # Bit strings
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Complex/Nested Types
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
create_table :complex_data do |t|
|
156
|
+
# List/Array columns
|
157
|
+
t.list :tags, element_type: :string
|
158
|
+
t.list :scores, element_type: :integer
|
159
|
+
|
160
|
+
# Struct columns (composite types)
|
161
|
+
t.struct :address, fields: {
|
162
|
+
street: :string,
|
163
|
+
city: :string,
|
164
|
+
zip: :integer,
|
165
|
+
country: :string
|
166
|
+
}
|
167
|
+
|
168
|
+
# Map columns (key-value pairs)
|
169
|
+
t.map :metadata, key_type: :string, value_type: :string
|
170
|
+
t.map :counters, key_type: :string, value_type: :integer
|
171
|
+
|
172
|
+
# Enum columns
|
173
|
+
t.enum :status, values: ['active', 'inactive', 'pending', 'archived']
|
174
|
+
t.enum :priority, values: ['low', 'medium', 'high', 'critical']
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
## Migration Examples
|
179
|
+
|
180
|
+
### Basic Table with Integer Primary Key
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class CreateUsers < ActiveRecord::Migration[7.0]
|
184
|
+
def change
|
185
|
+
# Standard Rails migration - works exactly like other databases
|
186
|
+
create_table :users do |t|
|
187
|
+
t.string :email, null: false
|
188
|
+
t.string :name
|
189
|
+
t.timestamps
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
### Table with UUID Primary Key
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
class CreateSessions < ActiveRecord::Migration[7.0]
|
199
|
+
def change
|
200
|
+
# Rails convention for UUID primary keys
|
201
|
+
create_table :sessions, id: :uuid do |t|
|
202
|
+
t.string :session_token
|
203
|
+
t.references :user, type: :bigint, foreign_key: true
|
204
|
+
t.timestamps
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
### Complex Data Types Example
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
class CreateProducts < ActiveRecord::Migration[7.0]
|
214
|
+
def change
|
215
|
+
# Standard Rails table with DuckDB-specific types
|
216
|
+
create_table :products do |t|
|
217
|
+
t.string :name, null: false
|
218
|
+
t.decimal :price, precision: 10, scale: 2
|
219
|
+
|
220
|
+
# DuckDB complex data types
|
221
|
+
t.list :tags, element_type: :string
|
222
|
+
t.struct :dimensions, fields: {
|
223
|
+
length: :decimal,
|
224
|
+
width: :decimal,
|
225
|
+
height: :decimal,
|
226
|
+
unit: :string
|
227
|
+
}
|
228
|
+
t.map :attributes, key_type: :string, value_type: :string
|
229
|
+
t.enum :status, values: ['draft', 'active', 'discontinued']
|
230
|
+
|
231
|
+
t.timestamps
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
### Working with Sequences in Existing Tables
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
class AddAutoIncrementToItems < ActiveRecord::Migration[7.0]
|
241
|
+
def change
|
242
|
+
# Create sequence and add column manually if needed
|
243
|
+
create_sequence 'items_serial_number_seq', start_with: 1
|
244
|
+
|
245
|
+
change_table :items do |t|
|
246
|
+
t.bigint :serial_number, default: -> { "nextval('items_serial_number_seq')" }
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
## Adapter Configuration
|
253
|
+
|
254
|
+
### Setting Default Primary Key Type
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# In your application configuration or initializer
|
258
|
+
# The adapter defaults to :bigint (Rails 5+ convention)
|
259
|
+
ActiveRecord::ConnectionAdapters::DuckdbAdapter.primary_key_type = :bigint
|
260
|
+
|
261
|
+
# To change default to UUID globally
|
262
|
+
ActiveRecord::ConnectionAdapters::DuckdbAdapter.primary_key_type = :uuid
|
263
|
+
|
264
|
+
# Individual tables can still override with id: :integer, id: :uuid, etc.
|
265
|
+
```
|
266
|
+
|
267
|
+
## Sequence Operations
|
268
|
+
|
269
|
+
### Working with Sequences in Code
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# Get next value from a sequence
|
273
|
+
next_id = User.connection.next_sequence_value('users_id_seq')
|
274
|
+
|
275
|
+
# Reset sequence to specific value
|
276
|
+
User.connection.reset_sequence!('users_id_seq', 1000)
|
277
|
+
|
278
|
+
# Check if sequence exists
|
279
|
+
if User.connection.sequence_exists?('users_id_seq')
|
280
|
+
# Do something with the sequence
|
281
|
+
end
|
282
|
+
|
283
|
+
# List all sequences
|
284
|
+
sequences = User.connection.sequences
|
285
|
+
```
|
286
|
+
|
287
|
+
## Best Practices
|
288
|
+
|
289
|
+
1. **Follow Rails conventions**: Use standard `create_table` with `id: :uuid` or `id: :bigint` options
|
290
|
+
2. **Use appropriate integer types**: Choose the smallest integer type that fits your needs
|
291
|
+
3. **Consider UUID for distributed systems**: Use `id: :uuid` for distributed applications
|
292
|
+
4. **Leverage complex types**: Use DuckDB's advanced types like `LIST` and `STRUCT` for analytical workloads
|
293
|
+
5. **Trust Rails defaults**: The adapter handles sequences automatically for integer primary keys
|
294
|
+
6. **Use manual sequences sparingly**: Only create custom sequences when you need specific behavior
|
295
|
+
|
296
|
+
## Troubleshooting
|
297
|
+
|
298
|
+
### Common Issues
|
299
|
+
|
300
|
+
1. **Sequence already exists**: If you get sequence already exists errors, check if the sequence was created from a previous migration
|
301
|
+
2. **UUID dependencies**: Make sure your application can generate UUIDs when using UUID primary keys
|
302
|
+
3. **Complex type support**: Verify your DuckDB version supports the complex types you're trying to use
|
303
|
+
|
304
|
+
### Debugging Sequences
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
# Check if a sequence exists
|
308
|
+
connection.sequence_exists?('users_id_seq')
|
309
|
+
# => true
|
310
|
+
|
311
|
+
# List all sequences
|
312
|
+
connection.sequences
|
313
|
+
# => ['users_id_seq', 'products_id_seq', ...]
|
314
|
+
|
315
|
+
# Standard Rails way - sequences are handled automatically
|
316
|
+
create_table :test_table do |t|
|
317
|
+
t.string :name
|
318
|
+
end
|
319
|
+
# Sequence creation is managed behind the scenes
|
320
|
+
```
|
321
|
+
|
322
|
+
This `TableDefinition` provides seamless Rails compatibility plus access to DuckDB's advanced features, making it easy to build both traditional Rails applications and analytical workloads with DuckDB.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Duckdb
|
6
|
+
class Column < ConnectionAdapters::Column
|
7
|
+
# Initialize a new DuckDB column with DuckDB-specific attributes
|
8
|
+
# @param auto_increment [Boolean, nil] whether this column is auto-incrementing
|
9
|
+
# @param rowid [Boolean, nil] whether this column is a rowid column
|
10
|
+
# @param generated_type [Symbol, nil] the type of generated column (:stored, :virtual)
|
11
|
+
# @param extra [String, nil] extra column definition information
|
12
|
+
def initialize(*, auto_increment: nil, rowid: nil, generated_type: nil, extra: nil, **)
|
13
|
+
super(*, **)
|
14
|
+
@auto_increment = auto_increment
|
15
|
+
@rowid = rowid
|
16
|
+
@generated_type = generated_type
|
17
|
+
@extra = extra
|
18
|
+
end
|
19
|
+
|
20
|
+
# Quotes a column name for use in SQL statements
|
21
|
+
# @param name [String, Symbol] The column name to quote
|
22
|
+
# @return [String] The quoted column name wrapped in double quotes
|
23
|
+
def quote_column_name(name)
|
24
|
+
%("#{name}")
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String, nil] extra column definition information
|
28
|
+
attr_reader :extra
|
29
|
+
# @return [Boolean, nil] whether this column is a rowid column
|
30
|
+
attr_reader :rowid
|
31
|
+
|
32
|
+
# Check if this column is a virtual/generated column
|
33
|
+
# https://duckdb.org/docs/stable/sql/statements/create_table#generated-columns
|
34
|
+
# TODO: Implement full virtual column support
|
35
|
+
# @return [Boolean] always returns false until virtual columns are fully implemented
|
36
|
+
def virtual?
|
37
|
+
# /\b(?:VIRTUAL|STORED|GENERATED)\b/.match?(extra)
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
# def virtual_stored?
|
42
|
+
# virtual? && @generated_type == :stored
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Check if this column has a default value
|
46
|
+
# @return [Boolean] true if the column has a default value
|
47
|
+
def has_default?
|
48
|
+
# super && !virtual?
|
49
|
+
!!super
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check if this column is auto-incrementing
|
53
|
+
# We probably need to check if the column is a UUID
|
54
|
+
# @return [Boolean] true if the column auto-increments
|
55
|
+
def auto_increment?
|
56
|
+
!!@auto_increment
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check if this column's value is automatically incremented by the database
|
60
|
+
# @return [Boolean] true if the column is auto-incremented by the database
|
61
|
+
def auto_incremented_by_db?
|
62
|
+
auto_increment? || !!rowid
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
# DuckDB Limits - https://duckdb.org/docs/stable/operations_manual/limits.html
|
6
|
+
module Duckdb
|
7
|
+
module DatabaseLimits
|
8
|
+
# Since DuckDb is PostgreSQL compatible (64-1), we can use the same limits as PostgreSQL.
|
9
|
+
# In the future want to drop possible PostgreSQL compatibility
|
10
|
+
# we can change the limits to match whatever limits DuckDB may want to impose
|
11
|
+
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
12
|
+
# Returns the maximum length for database identifiers
|
13
|
+
# @return [Integer] The maximum identifier length (63 characters for PostgreSQL compatibility)
|
14
|
+
def max_identifier_length
|
15
|
+
63
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the maximum length for table aliases
|
19
|
+
# @return [Integer] The maximum table alias length (delegates to max_identifier_length)
|
20
|
+
def table_alias_length
|
21
|
+
max_identifier_length
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the maximum length for table names
|
25
|
+
# @return [Integer] The maximum table name length (delegates to max_identifier_length)
|
26
|
+
def table_name_length
|
27
|
+
max_identifier_length
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the maximum length for index names
|
31
|
+
# @return [Integer] The maximum index name length (delegates to max_identifier_length)
|
32
|
+
def index_name_length
|
33
|
+
max_identifier_length
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# The max number of binds is 1024
|
39
|
+
# Returns the maximum number of bind parameters for queries
|
40
|
+
# @return [Integer] The maximum number of bind parameters (1000)
|
41
|
+
def bind_params_length
|
42
|
+
1_000
|
43
|
+
end
|
44
|
+
|
45
|
+
# https://duckdb.org/docs/stable/data/insert.html
|
46
|
+
# Returns the maximum number of rows that can be inserted in a single operation
|
47
|
+
# @return [Integer] The maximum number of rows for batch inserts (1000)
|
48
|
+
def insert_rows_length
|
49
|
+
1_000
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|