sequel-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/.kiro/specs/advanced-sql-features-implementation/design.md +24 -0
- data/.kiro/specs/advanced-sql-features-implementation/requirements.md +43 -0
- data/.kiro/specs/advanced-sql-features-implementation/tasks.md +24 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +258 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +84 -0
- data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +94 -0
- data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +32 -0
- data/.kiro/specs/integration-test-database-setup/design.md +0 -0
- data/.kiro/specs/integration-test-database-setup/requirements.md +117 -0
- data/.kiro/specs/sequel-duckdb-adapter/design.md +542 -0
- data/.kiro/specs/sequel-duckdb-adapter/requirements.md +202 -0
- data/.kiro/specs/sequel-duckdb-adapter/tasks.md +247 -0
- data/.kiro/specs/sql-expression-handling-fix/design.md +298 -0
- data/.kiro/specs/sql-expression-handling-fix/requirements.md +86 -0
- data/.kiro/specs/sql-expression-handling-fix/tasks.md +22 -0
- data/.kiro/specs/test-infrastructure-improvements/requirements.md +106 -0
- data/.kiro/steering/product.md +22 -0
- data/.kiro/steering/structure.md +88 -0
- data/.kiro/steering/tech.md +124 -0
- data/.kiro/steering/testing.md +192 -0
- data/.rubocop.yml +103 -0
- data/.yardopts +8 -0
- data/API_DOCUMENTATION.md +919 -0
- data/CHANGELOG.md +131 -0
- data/LICENSE +21 -0
- data/MIGRATION_EXAMPLES.md +740 -0
- data/PERFORMANCE_OPTIMIZATIONS.md +723 -0
- data/README.md +692 -0
- data/Rakefile +27 -0
- data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +164 -0
- data/docs/DUCKDB_SQL_PATTERNS.md +410 -0
- data/docs/TASK_12_VERIFICATION_SUMMARY.md +122 -0
- data/lib/sequel/adapters/duckdb.rb +256 -0
- data/lib/sequel/adapters/shared/duckdb.rb +2349 -0
- data/lib/sequel/duckdb/version.rb +16 -0
- data/lib/sequel/duckdb.rb +43 -0
- data/sig/sequel/duckdb.rbs +6 -0
- metadata +235 -0
@@ -0,0 +1,919 @@
|
|
1
|
+
# API Documentation - Sequel DuckDB Adapter
|
2
|
+
|
3
|
+
This document provides comprehensive API documentation for the Sequel DuckDB adapter, including all public methods, configuration options, and usage patterns.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
1. [Version Compatibility](#version-compatibility)
|
8
|
+
2. [Database Class API](#database-class-api)
|
9
|
+
3. [Dataset Class API](#dataset-class-api)
|
10
|
+
4. [SQL Generation Patterns](#sql-generation-patterns)
|
11
|
+
5. [Configuration Options](#configuration-options)
|
12
|
+
6. [Error Handling](#error-handling)
|
13
|
+
7. [Data Type Mappings](#data-type-mappings)
|
14
|
+
8. [Performance Tuning](#performance-tuning)
|
15
|
+
|
16
|
+
## Version Compatibility
|
17
|
+
|
18
|
+
### Supported Versions
|
19
|
+
|
20
|
+
| Component | Minimum Version | Recommended Version | Notes |
|
21
|
+
|-----------|----------------|-------------------|-------|
|
22
|
+
| Ruby | 3.1.0 | 3.2.0+ | Required for modern syntax and performance |
|
23
|
+
| Sequel | 5.0.0 | 5.70.0+ | Core ORM functionality |
|
24
|
+
| DuckDB | 0.8.0 | 0.9.0+ | Database engine |
|
25
|
+
| ruby-duckdb | 1.0.0 | 1.0.0+ | Ruby client library |
|
26
|
+
|
27
|
+
### Ruby Version Support
|
28
|
+
|
29
|
+
- **Ruby 3.1.0+**: Full support with all features
|
30
|
+
- **Ruby 3.2.0+**: Recommended for best performance
|
31
|
+
- **Ruby 3.3.0+**: Latest features and optimizations
|
32
|
+
|
33
|
+
### Sequel Version Support
|
34
|
+
|
35
|
+
- **Sequel 5.0+**: Basic functionality
|
36
|
+
- **Sequel 5.50+**: Enhanced schema introspection
|
37
|
+
- **Sequel 5.70+**: Full feature compatibility
|
38
|
+
|
39
|
+
### DuckDB Version Support
|
40
|
+
|
41
|
+
- **DuckDB 0.8.0+**: Core functionality
|
42
|
+
- **DuckDB 0.9.0+**: Enhanced JSON and array support
|
43
|
+
- **DuckDB 0.10.0+**: Latest analytical features
|
44
|
+
|
45
|
+
## Database Class API
|
46
|
+
|
47
|
+
### Connection Methods
|
48
|
+
|
49
|
+
#### `Sequel.connect(connection_string)`
|
50
|
+
|
51
|
+
Connect to a DuckDB database using a connection string.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# In-memory database
|
55
|
+
db = Sequel.connect('duckdb::memory:')
|
56
|
+
|
57
|
+
# File database
|
58
|
+
db = Sequel.connect('duckdb:///path/to/database.duckdb')
|
59
|
+
db = Sequel.connect('duckdb://relative/path/to/database.duckdb')
|
60
|
+
|
61
|
+
# With query parameters
|
62
|
+
db = Sequel.connect('duckdb:///path/to/database.duckdb?readonly=true')
|
63
|
+
```
|
64
|
+
|
65
|
+
**Parameters:**
|
66
|
+
- `connection_string` (String): DuckDB connection string
|
67
|
+
|
68
|
+
**Returns:** `Sequel::DuckDB::Database` instance
|
69
|
+
|
70
|
+
**Raises:** `Sequel::DatabaseConnectionError` if connection fails
|
71
|
+
|
72
|
+
#### `Sequel.connect(options_hash)`
|
73
|
+
|
74
|
+
Connect using a configuration hash.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
db = Sequel.connect(
|
78
|
+
adapter: 'duckdb',
|
79
|
+
database: '/path/to/database.duckdb',
|
80
|
+
readonly: false,
|
81
|
+
config: {
|
82
|
+
memory_limit: '4GB',
|
83
|
+
threads: 8,
|
84
|
+
temp_directory: '/tmp/duckdb'
|
85
|
+
}
|
86
|
+
)
|
87
|
+
```
|
88
|
+
|
89
|
+
**Parameters:**
|
90
|
+
- `options_hash` (Hash): Configuration options
|
91
|
+
- `:adapter` (String): Must be 'duckdb'
|
92
|
+
- `:database` (String): Database path or ':memory:'
|
93
|
+
- `:readonly` (Boolean): Read-only mode (default: false)
|
94
|
+
- `:config` (Hash): DuckDB-specific configuration
|
95
|
+
|
96
|
+
**Returns:** `Sequel::DuckDB::Database` instance
|
97
|
+
|
98
|
+
### Schema Introspection Methods
|
99
|
+
|
100
|
+
#### `#tables(options = {})`
|
101
|
+
|
102
|
+
Get list of all tables in the database.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
db.tables
|
106
|
+
# => [:users, :products, :orders]
|
107
|
+
|
108
|
+
# With schema specification
|
109
|
+
db.tables(schema: 'main')
|
110
|
+
# => [:users, :products, :orders]
|
111
|
+
```
|
112
|
+
|
113
|
+
**Parameters:**
|
114
|
+
- `options` (Hash): Optional parameters
|
115
|
+
- `:schema` (String): Schema name (default: 'main')
|
116
|
+
|
117
|
+
**Returns:** Array of table names as symbols
|
118
|
+
|
119
|
+
#### `#schema(table_name, options = {})`
|
120
|
+
|
121
|
+
Get detailed schema information for a table.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
db.schema(:users)
|
125
|
+
# => [
|
126
|
+
# [:id, {type: :integer, db_type: "INTEGER", primary_key: true, allow_null: false}],
|
127
|
+
# [:name, {type: :string, db_type: "VARCHAR", primary_key: false, allow_null: false}],
|
128
|
+
# [:email, {type: :string, db_type: "VARCHAR", primary_key: false, allow_null: true}]
|
129
|
+
# ]
|
130
|
+
```
|
131
|
+
|
132
|
+
**Parameters:**
|
133
|
+
- `table_name` (Symbol/String): Name of the table
|
134
|
+
- `options` (Hash): Optional parameters
|
135
|
+
- `:schema` (String): Schema name (default: 'main')
|
136
|
+
|
137
|
+
**Returns:** Array of `[column_name, column_info]` pairs
|
138
|
+
|
139
|
+
**Column Info Hash:**
|
140
|
+
- `:type` (Symbol): Sequel type (:integer, :string, :boolean, etc.)
|
141
|
+
- `:db_type` (String): DuckDB native type
|
142
|
+
- `:primary_key` (Boolean): Whether column is part of primary key
|
143
|
+
- `:allow_null` (Boolean): Whether column allows NULL values
|
144
|
+
- `:default` (Object): Default value or nil
|
145
|
+
- `:max_length` (Integer): Maximum length for string types
|
146
|
+
- `:precision` (Integer): Precision for numeric types
|
147
|
+
- `:scale` (Integer): Scale for decimal types
|
148
|
+
|
149
|
+
#### `#indexes(table_name, options = {})`
|
150
|
+
|
151
|
+
Get index information for a table.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
db.indexes(:users)
|
155
|
+
# => {
|
156
|
+
# :users_email_index => {
|
157
|
+
# columns: [:email],
|
158
|
+
# unique: true,
|
159
|
+
# primary: false
|
160
|
+
# }
|
161
|
+
# }
|
162
|
+
```
|
163
|
+
|
164
|
+
**Parameters:**
|
165
|
+
- `table_name` (Symbol/String): Name of the table
|
166
|
+
- `options` (Hash): Optional parameters
|
167
|
+
|
168
|
+
**Returns:** Hash of `index_name => index_info`
|
169
|
+
|
170
|
+
#### `#table_exists?(table_name, options = {})`
|
171
|
+
|
172
|
+
Check if a table exists.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
db.table_exists?(:users) # => true
|
176
|
+
db.table_exists?(:nonexistent) # => false
|
177
|
+
```
|
178
|
+
|
179
|
+
**Parameters:**
|
180
|
+
- `table_name` (Symbol/String): Name of the table
|
181
|
+
- `options` (Hash): Optional parameters
|
182
|
+
|
183
|
+
**Returns:** Boolean
|
184
|
+
|
185
|
+
### SQL Execution Methods
|
186
|
+
|
187
|
+
#### `#execute(sql, options = {})`
|
188
|
+
|
189
|
+
Execute raw SQL statement.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
# Simple query
|
193
|
+
result = db.execute("SELECT COUNT(*) FROM users")
|
194
|
+
|
195
|
+
# With parameters
|
196
|
+
result = db.execute("SELECT * FROM users WHERE age > ?", [25])
|
197
|
+
|
198
|
+
# With block for result processing
|
199
|
+
db.execute("SELECT * FROM users") do |row|
|
200
|
+
puts row[:name]
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
**Parameters:**
|
205
|
+
- `sql` (String): SQL statement to execute
|
206
|
+
- `options` (Hash/Array): Parameters or options
|
207
|
+
- If Array: Parameters for prepared statement
|
208
|
+
- If Hash: Options including `:params` key
|
209
|
+
|
210
|
+
**Returns:** Query result or number of affected rows
|
211
|
+
|
212
|
+
#### `#execute_insert(sql, options = {})`
|
213
|
+
|
214
|
+
Execute INSERT statement.
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
db.execute_insert("INSERT INTO users (name, email) VALUES (?, ?)", ['John', 'john@example.com'])
|
218
|
+
```
|
219
|
+
|
220
|
+
**Parameters:**
|
221
|
+
- `sql` (String): INSERT SQL statement
|
222
|
+
- `options` (Hash): Options for execution
|
223
|
+
|
224
|
+
**Returns:** Inserted record ID (if available) or nil
|
225
|
+
|
226
|
+
#### `#execute_update(sql, options = {})`
|
227
|
+
|
228
|
+
Execute UPDATE statement.
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
affected_rows = db.execute_update("UPDATE users SET active = ? WHERE age > ?", [true, 25])
|
232
|
+
```
|
233
|
+
|
234
|
+
**Parameters:**
|
235
|
+
- `sql` (String): UPDATE SQL statement
|
236
|
+
- `options` (Hash): Options for execution
|
237
|
+
|
238
|
+
**Returns:** Number of affected rows
|
239
|
+
|
240
|
+
### Transaction Methods
|
241
|
+
|
242
|
+
#### `#transaction(options = {}, &block)`
|
243
|
+
|
244
|
+
Execute a block within a database transaction.
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
# Basic transaction
|
248
|
+
db.transaction do
|
249
|
+
db[:users].insert(name: 'Alice', email: 'alice@example.com')
|
250
|
+
db[:profiles].insert(user_id: db[:users].max(:id), bio: 'Developer')
|
251
|
+
end
|
252
|
+
|
253
|
+
# With rollback
|
254
|
+
db.transaction do
|
255
|
+
db[:users].insert(name: 'Bob', email: 'bob@example.com')
|
256
|
+
raise Sequel::Rollback if some_condition
|
257
|
+
end
|
258
|
+
|
259
|
+
# With savepoint (nested transaction)
|
260
|
+
db.transaction do
|
261
|
+
db[:users].insert(name: 'Charlie', email: 'charlie@example.com')
|
262
|
+
|
263
|
+
db.transaction(savepoint: true) do
|
264
|
+
# This can be rolled back independently
|
265
|
+
db[:audit_log].insert(action: 'user_created')
|
266
|
+
end
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
**Parameters:**
|
271
|
+
- `options` (Hash): Transaction options
|
272
|
+
- `:savepoint` (Boolean): Use savepoint for nested transaction
|
273
|
+
- `:isolation` (Symbol): Transaction isolation level
|
274
|
+
- `:server` (Symbol): Server/connection to use
|
275
|
+
|
276
|
+
**Returns:** Result of the block
|
277
|
+
|
278
|
+
**Raises:**
|
279
|
+
- `Sequel::Rollback`: To rollback transaction
|
280
|
+
- `Sequel::DatabaseError`: On transaction errors
|
281
|
+
|
282
|
+
### Connection Management
|
283
|
+
|
284
|
+
#### `#disconnect`
|
285
|
+
|
286
|
+
Close all database connections.
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
db.disconnect
|
290
|
+
```
|
291
|
+
|
292
|
+
#### `#test_connection`
|
293
|
+
|
294
|
+
Test if the database connection is working.
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
db.test_connection # => true
|
298
|
+
```
|
299
|
+
|
300
|
+
**Returns:** Boolean indicating connection status
|
301
|
+
|
302
|
+
## Dataset Class API
|
303
|
+
|
304
|
+
### Query Building Methods
|
305
|
+
|
306
|
+
#### `#where(conditions)`
|
307
|
+
|
308
|
+
Add WHERE clause to query.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
users = db[:users]
|
312
|
+
|
313
|
+
# Hash conditions
|
314
|
+
users.where(active: true, age: 25)
|
315
|
+
|
316
|
+
# Block conditions
|
317
|
+
users.where { age > 25 }
|
318
|
+
|
319
|
+
# String conditions with parameters
|
320
|
+
users.where("name LIKE ?", 'John%')
|
321
|
+
|
322
|
+
# Complex conditions
|
323
|
+
users.where(Sequel.like(:name, 'John%') & (Sequel[:age] > 25))
|
324
|
+
```
|
325
|
+
|
326
|
+
**Parameters:**
|
327
|
+
- `conditions`: Various condition formats (Hash, String, Block, Sequel expressions)
|
328
|
+
|
329
|
+
**Returns:** New Dataset with WHERE clause added
|
330
|
+
|
331
|
+
#### `#select(*columns)`
|
332
|
+
|
333
|
+
Specify columns to select.
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
users = db[:users]
|
337
|
+
|
338
|
+
# Select specific columns
|
339
|
+
users.select(:id, :name, :email)
|
340
|
+
|
341
|
+
# Select with aliases
|
342
|
+
users.select(:id, Sequel[:name].as(:full_name))
|
343
|
+
|
344
|
+
# Select with functions
|
345
|
+
users.select(:id, Sequel.function(:upper, :name).as(:name_upper))
|
346
|
+
```
|
347
|
+
|
348
|
+
**Parameters:**
|
349
|
+
- `columns`: Column names, expressions, or functions
|
350
|
+
|
351
|
+
**Returns:** New Dataset with SELECT clause
|
352
|
+
|
353
|
+
#### `#order(*columns)`
|
354
|
+
|
355
|
+
Add ORDER BY clause.
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
users = db[:users]
|
359
|
+
|
360
|
+
# Simple ordering
|
361
|
+
users.order(:name)
|
362
|
+
|
363
|
+
# Multiple columns
|
364
|
+
users.order(:name, :created_at)
|
365
|
+
|
366
|
+
# Descending order
|
367
|
+
users.order(Sequel.desc(:created_at))
|
368
|
+
|
369
|
+
# Mixed ordering
|
370
|
+
users.order(:name, Sequel.desc(:created_at))
|
371
|
+
```
|
372
|
+
|
373
|
+
**Parameters:**
|
374
|
+
- `columns`: Column names or ordering expressions
|
375
|
+
|
376
|
+
**Returns:** New Dataset with ORDER BY clause
|
377
|
+
|
378
|
+
#### `#limit(count, offset = nil)`
|
379
|
+
|
380
|
+
Add LIMIT and optional OFFSET.
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
users = db[:users]
|
384
|
+
|
385
|
+
# Limit only
|
386
|
+
users.limit(10)
|
387
|
+
|
388
|
+
# Limit with offset
|
389
|
+
users.limit(10, 20)
|
390
|
+
|
391
|
+
# Pagination helper
|
392
|
+
users.paginate(page: 2, per_page: 10)
|
393
|
+
```
|
394
|
+
|
395
|
+
**Parameters:**
|
396
|
+
- `count` (Integer): Maximum number of rows
|
397
|
+
- `offset` (Integer): Number of rows to skip
|
398
|
+
|
399
|
+
**Returns:** New Dataset with LIMIT clause
|
400
|
+
|
401
|
+
#### `#group(*columns)`
|
402
|
+
|
403
|
+
Add GROUP BY clause.
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
orders = db[:orders]
|
407
|
+
|
408
|
+
# Group by single column
|
409
|
+
orders.group(:status)
|
410
|
+
|
411
|
+
# Group by multiple columns
|
412
|
+
orders.group(:status, :user_id)
|
413
|
+
|
414
|
+
# With aggregation
|
415
|
+
orders.group(:status).select(:status, Sequel.count(:id).as(:count))
|
416
|
+
```
|
417
|
+
|
418
|
+
**Parameters:**
|
419
|
+
- `columns`: Column names to group by
|
420
|
+
|
421
|
+
**Returns:** New Dataset with GROUP BY clause
|
422
|
+
|
423
|
+
#### `#having(conditions)`
|
424
|
+
|
425
|
+
Add HAVING clause (used with GROUP BY).
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
orders = db[:orders]
|
429
|
+
|
430
|
+
orders.group(:user_id)
|
431
|
+
.select(:user_id, Sequel.sum(:total).as(:total_spent))
|
432
|
+
.having { sum(:total) > 1000 }
|
433
|
+
```
|
434
|
+
|
435
|
+
**Parameters:**
|
436
|
+
- `conditions`: Conditions for HAVING clause
|
437
|
+
|
438
|
+
**Returns:** New Dataset with HAVING clause
|
439
|
+
|
440
|
+
### Join Methods
|
441
|
+
|
442
|
+
#### `#join(table, conditions = nil, options = {})`
|
443
|
+
|
444
|
+
Add INNER JOIN.
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
users = db[:users]
|
448
|
+
|
449
|
+
# Simple join
|
450
|
+
users.join(:orders, user_id: :id)
|
451
|
+
|
452
|
+
# Join with table aliases
|
453
|
+
users.join(:orders___o, user_id: :id)
|
454
|
+
|
455
|
+
# Complex join conditions
|
456
|
+
users.join(:orders, Sequel[:orders][:user_id] => Sequel[:users][:id])
|
457
|
+
```
|
458
|
+
|
459
|
+
**Parameters:**
|
460
|
+
- `table`: Table to join (Symbol/String)
|
461
|
+
- `conditions`: Join conditions (Hash or Sequel expression)
|
462
|
+
- `options`: Join options
|
463
|
+
|
464
|
+
**Returns:** New Dataset with JOIN clause
|
465
|
+
|
466
|
+
#### `#left_join(table, conditions = nil, options = {})`
|
467
|
+
|
468
|
+
Add LEFT OUTER JOIN.
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
users.left_join(:profiles, user_id: :id)
|
472
|
+
```
|
473
|
+
|
474
|
+
#### `#right_join(table, conditions = nil, options = {})`
|
475
|
+
|
476
|
+
Add RIGHT OUTER JOIN.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
users.right_join(:orders, user_id: :id)
|
480
|
+
```
|
481
|
+
|
482
|
+
#### `#full_join(table, conditions = nil, options = {})`
|
483
|
+
|
484
|
+
Add FULL OUTER JOIN.
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
users.full_join(:profiles, user_id: :id)
|
488
|
+
```
|
489
|
+
|
490
|
+
### Data Retrieval Methods
|
491
|
+
|
492
|
+
#### `#all`
|
493
|
+
|
494
|
+
Retrieve all matching records.
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
users = db[:users].where(active: true).all
|
498
|
+
# => [{id: 1, name: 'John', ...}, {id: 2, name: 'Jane', ...}]
|
499
|
+
```
|
500
|
+
|
501
|
+
**Returns:** Array of record hashes
|
502
|
+
|
503
|
+
#### `#first`
|
504
|
+
|
505
|
+
Retrieve first matching record.
|
506
|
+
|
507
|
+
```ruby
|
508
|
+
user = db[:users].where(email: 'john@example.com').first
|
509
|
+
# => {id: 1, name: 'John', email: 'john@example.com', ...}
|
510
|
+
```
|
511
|
+
|
512
|
+
**Returns:** Record hash or nil if not found
|
513
|
+
|
514
|
+
#### `#last`
|
515
|
+
|
516
|
+
Retrieve last matching record (requires ORDER BY).
|
517
|
+
|
518
|
+
```ruby
|
519
|
+
user = db[:users].order(:created_at).last
|
520
|
+
```
|
521
|
+
|
522
|
+
**Returns:** Record hash or nil if not found
|
523
|
+
|
524
|
+
#### `#count`
|
525
|
+
|
526
|
+
Count matching records.
|
527
|
+
|
528
|
+
```ruby
|
529
|
+
count = db[:users].where(active: true).count
|
530
|
+
# => 42
|
531
|
+
```
|
532
|
+
|
533
|
+
**Returns:** Integer count
|
534
|
+
|
535
|
+
#### `#each(&block)`
|
536
|
+
|
537
|
+
Iterate over all matching records.
|
538
|
+
|
539
|
+
```ruby
|
540
|
+
db[:users].where(active: true).each do |user|
|
541
|
+
puts user[:name]
|
542
|
+
end
|
543
|
+
```
|
544
|
+
|
545
|
+
**Parameters:**
|
546
|
+
- `block`: Block to execute for each record
|
547
|
+
|
548
|
+
**Returns:** Dataset (for chaining)
|
549
|
+
|
550
|
+
#### `#paged_each(options = {}, &block)`
|
551
|
+
|
552
|
+
Iterate over records in batches for memory efficiency.
|
553
|
+
|
554
|
+
```ruby
|
555
|
+
db[:large_table].paged_each(rows_per_fetch: 1000) do |row|
|
556
|
+
process_row(row)
|
557
|
+
end
|
558
|
+
```
|
559
|
+
|
560
|
+
**Parameters:**
|
561
|
+
- `options` (Hash): Paging options
|
562
|
+
- `:rows_per_fetch` (Integer): Batch size (default: 1000)
|
563
|
+
- `block`: Block to execute for each record
|
564
|
+
|
565
|
+
### Data Modification Methods
|
566
|
+
|
567
|
+
#### `#insert(values)`
|
568
|
+
|
569
|
+
Insert a single record.
|
570
|
+
|
571
|
+
```ruby
|
572
|
+
user_id = db[:users].insert(
|
573
|
+
name: 'John Doe',
|
574
|
+
email: 'john@example.com',
|
575
|
+
created_at: Time.now
|
576
|
+
)
|
577
|
+
```
|
578
|
+
|
579
|
+
**Parameters:**
|
580
|
+
- `values` (Hash): Column values to insert
|
581
|
+
|
582
|
+
**Returns:** Inserted record ID (if available)
|
583
|
+
|
584
|
+
#### `#multi_insert(array)`
|
585
|
+
|
586
|
+
Insert multiple records efficiently.
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
db[:users].multi_insert([
|
590
|
+
{name: 'Alice', email: 'alice@example.com'},
|
591
|
+
{name: 'Bob', email: 'bob@example.com'},
|
592
|
+
{name: 'Charlie', email: 'charlie@example.com'}
|
593
|
+
])
|
594
|
+
```
|
595
|
+
|
596
|
+
**Parameters:**
|
597
|
+
- `array` (Array): Array of record hashes
|
598
|
+
|
599
|
+
**Returns:** Number of inserted records
|
600
|
+
|
601
|
+
#### `#update(values)`
|
602
|
+
|
603
|
+
Update matching records.
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
affected_rows = db[:users]
|
607
|
+
.where(active: false)
|
608
|
+
.update(active: true, updated_at: Time.now)
|
609
|
+
```
|
610
|
+
|
611
|
+
**Parameters:**
|
612
|
+
- `values` (Hash): Column values to update
|
613
|
+
|
614
|
+
**Returns:** Number of affected rows
|
615
|
+
|
616
|
+
#### `#delete`
|
617
|
+
|
618
|
+
Delete matching records.
|
619
|
+
|
620
|
+
```ruby
|
621
|
+
deleted_count = db[:users].where { created_at < Date.today - 365 }.delete
|
622
|
+
```
|
623
|
+
|
624
|
+
**Returns:** Number of deleted rows
|
625
|
+
|
626
|
+
### Analytical Methods (DuckDB-Specific)
|
627
|
+
|
628
|
+
#### Window Functions
|
629
|
+
|
630
|
+
```ruby
|
631
|
+
# Ranking within groups
|
632
|
+
db[:sales].select(
|
633
|
+
:product_id,
|
634
|
+
:amount,
|
635
|
+
Sequel.function(:rank).over(
|
636
|
+
partition: :category_id,
|
637
|
+
order: Sequel.desc(:amount)
|
638
|
+
).as(:rank)
|
639
|
+
)
|
640
|
+
|
641
|
+
# Running totals
|
642
|
+
db[:sales].select(
|
643
|
+
:date,
|
644
|
+
:amount,
|
645
|
+
Sequel.function(:sum, :amount).over(
|
646
|
+
order: :date
|
647
|
+
).as(:running_total)
|
648
|
+
)
|
649
|
+
```
|
650
|
+
|
651
|
+
#### Common Table Expressions (CTEs)
|
652
|
+
|
653
|
+
```ruby
|
654
|
+
# Simple CTE
|
655
|
+
db.with(:high_spenders,
|
656
|
+
db[:orders].group(:user_id).having { sum(:total) > 1000 }.select(:user_id)
|
657
|
+
).from(:high_spenders).join(:users, id: :user_id)
|
658
|
+
|
659
|
+
# Recursive CTE
|
660
|
+
db.with_recursive(:category_tree,
|
661
|
+
db[:categories].where(parent_id: nil),
|
662
|
+
db[:categories].join(:category_tree, parent_id: :id)
|
663
|
+
).from(:category_tree)
|
664
|
+
```
|
665
|
+
|
666
|
+
## SQL Generation Patterns
|
667
|
+
|
668
|
+
The sequel-duckdb adapter generates SQL optimized for DuckDB's analytical capabilities while maintaining compatibility with Sequel conventions. Understanding these patterns helps developers write efficient queries and troubleshoot issues.
|
669
|
+
|
670
|
+
### Key SQL Pattern Features
|
671
|
+
|
672
|
+
- **Clean LIKE clauses** without unnecessary ESCAPE clauses
|
673
|
+
- **ILIKE conversion** to UPPER() LIKE UPPER() for case-insensitive matching
|
674
|
+
- **Regex support** using DuckDB's regexp_matches() function
|
675
|
+
- **Qualified column references** using standard dot notation
|
676
|
+
- **Automatic recursive CTE detection** for WITH RECURSIVE syntax
|
677
|
+
- **Proper expression parentheses** for correct operator precedence
|
678
|
+
|
679
|
+
### Quick Reference
|
680
|
+
|
681
|
+
```ruby
|
682
|
+
# LIKE patterns (clean syntax)
|
683
|
+
dataset.where(Sequel.like(:name, "%John%"))
|
684
|
+
# SQL: SELECT * FROM users WHERE (name LIKE '%John%')
|
685
|
+
|
686
|
+
# ILIKE patterns (case-insensitive)
|
687
|
+
dataset.where(Sequel.ilike(:name, "%john%"))
|
688
|
+
# SQL: SELECT * FROM users WHERE (UPPER(name) LIKE UPPER('%john%'))
|
689
|
+
|
690
|
+
# Regex patterns
|
691
|
+
dataset.where(name: /^John/)
|
692
|
+
# SQL: SELECT * FROM users WHERE (regexp_matches(name, '^John'))
|
693
|
+
|
694
|
+
# Qualified column references
|
695
|
+
dataset.join(:profiles, user_id: :id)
|
696
|
+
# SQL: SELECT * FROM users INNER JOIN profiles ON (profiles.user_id = users.id)
|
697
|
+
|
698
|
+
# Recursive CTEs (auto-detected)
|
699
|
+
base_case = db.select(Sequel.as(1, :n))
|
700
|
+
recursive_case = db[:t].select(Sequel.lit("n + 1")).where { n < 10 }
|
701
|
+
combined = base_case.union(recursive_case, all: true)
|
702
|
+
dataset.with(:t, combined).from(:t)
|
703
|
+
# SQL: WITH RECURSIVE t AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM t WHERE (n < 10)) SELECT * FROM t
|
704
|
+
```
|
705
|
+
|
706
|
+
### Detailed Documentation
|
707
|
+
|
708
|
+
For comprehensive documentation of all SQL patterns, including design decisions and troubleshooting tips, see [DUCKDB_SQL_PATTERNS.md](DUCKDB_SQL_PATTERNS.md).
|
709
|
+
|
710
|
+
## Configuration Options
|
711
|
+
|
712
|
+
### Database Configuration
|
713
|
+
|
714
|
+
```ruby
|
715
|
+
db = Sequel.connect(
|
716
|
+
adapter: 'duckdb',
|
717
|
+
database: '/path/to/database.duckdb',
|
718
|
+
|
719
|
+
# Connection options
|
720
|
+
readonly: false,
|
721
|
+
|
722
|
+
# DuckDB-specific configuration
|
723
|
+
config: {
|
724
|
+
# Memory management
|
725
|
+
memory_limit: '4GB', # Maximum memory usage
|
726
|
+
max_memory: '8GB', # Memory limit before spilling to disk
|
727
|
+
temp_directory: '/tmp/duckdb', # Temporary file location
|
728
|
+
|
729
|
+
# Performance tuning
|
730
|
+
threads: 8, # Number of threads for parallel processing
|
731
|
+
enable_optimizer: true, # Enable query optimizer
|
732
|
+
enable_profiling: false, # Enable query profiling
|
733
|
+
|
734
|
+
# Behavioral settings
|
735
|
+
default_order: 'ASC', # Default sort order
|
736
|
+
preserve_insertion_order: false, # Preserve insertion order
|
737
|
+
|
738
|
+
# Extension settings
|
739
|
+
autoload_known_extensions: true, # Auto-load known extensions
|
740
|
+
autoinstall_known_extensions: false # Auto-install extensions
|
741
|
+
},
|
742
|
+
|
743
|
+
# Sequel connection pool options
|
744
|
+
max_connections: 10, # Connection pool size
|
745
|
+
pool_timeout: 5, # Connection timeout in seconds
|
746
|
+
pool_sleep_time: 0.001, # Sleep time between connection retries
|
747
|
+
pool_connection_validation: true # Validate connections before use
|
748
|
+
)
|
749
|
+
```
|
750
|
+
|
751
|
+
### Runtime Configuration
|
752
|
+
|
753
|
+
```ruby
|
754
|
+
# Change settings at runtime
|
755
|
+
db.run "SET memory_limit='2GB'"
|
756
|
+
db.run "SET threads=4"
|
757
|
+
db.run "SET enable_profiling=true"
|
758
|
+
|
759
|
+
# Check current settings
|
760
|
+
db.fetch("SELECT * FROM duckdb_settings()").all
|
761
|
+
```
|
762
|
+
|
763
|
+
## Error Handling
|
764
|
+
|
765
|
+
### Exception Hierarchy
|
766
|
+
|
767
|
+
The adapter maps DuckDB errors to appropriate Sequel exception types:
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
begin
|
771
|
+
db[:users].insert(name: nil) # NOT NULL violation
|
772
|
+
rescue Sequel::NotNullConstraintViolation => e
|
773
|
+
puts "Cannot insert null name: #{e.message}"
|
774
|
+
rescue Sequel::UniqueConstraintViolation => e
|
775
|
+
puts "Duplicate value: #{e.message}"
|
776
|
+
rescue Sequel::ForeignKeyConstraintViolation => e
|
777
|
+
puts "Foreign key violation: #{e.message}"
|
778
|
+
rescue Sequel::CheckConstraintViolation => e
|
779
|
+
puts "Check constraint failed: #{e.message}"
|
780
|
+
rescue Sequel::ConstraintViolation => e
|
781
|
+
puts "Constraint violation: #{e.message}"
|
782
|
+
rescue Sequel::DatabaseConnectionError => e
|
783
|
+
puts "Connection error: #{e.message}"
|
784
|
+
rescue Sequel::DatabaseError => e
|
785
|
+
puts "Database error: #{e.message}"
|
786
|
+
end
|
787
|
+
```
|
788
|
+
|
789
|
+
### Error Types
|
790
|
+
|
791
|
+
| Sequel Exception | DuckDB Error Patterns | Description |
|
792
|
+
|------------------|----------------------|-------------|
|
793
|
+
| `NotNullConstraintViolation` | `violates not null`, `null value not allowed` | NOT NULL constraint violations |
|
794
|
+
| `UniqueConstraintViolation` | `unique constraint`, `duplicate key` | UNIQUE constraint violations |
|
795
|
+
| `ForeignKeyConstraintViolation` | `foreign key constraint`, `violates foreign key` | Foreign key violations |
|
796
|
+
| `CheckConstraintViolation` | `check constraint`, `violates check` | CHECK constraint violations |
|
797
|
+
| `ConstraintViolation` | `constraint violation` | Generic constraint violations |
|
798
|
+
| `DatabaseConnectionError` | `connection`, `cannot open`, `database not found` | Connection-related errors |
|
799
|
+
| `DatabaseError` | `syntax error`, `parse error`, `table does not exist` | General database errors |
|
800
|
+
|
801
|
+
## Data Type Mappings
|
802
|
+
|
803
|
+
### Ruby to DuckDB Type Mapping
|
804
|
+
|
805
|
+
| Ruby Type | DuckDB Type | Notes |
|
806
|
+
|-----------|-------------|-------|
|
807
|
+
| `String` | `VARCHAR` | Default string type |
|
808
|
+
| `String` (large) | `TEXT` | For long text content |
|
809
|
+
| `Integer` | `INTEGER` | 32-bit signed integer |
|
810
|
+
| `Integer` (large) | `BIGINT` | 64-bit signed integer |
|
811
|
+
| `Float` | `DOUBLE` | Double precision floating point |
|
812
|
+
| `BigDecimal` | `DECIMAL` | Exact numeric with precision/scale |
|
813
|
+
| `TrueClass/FalseClass` | `BOOLEAN` | Native boolean type |
|
814
|
+
| `Date` | `DATE` | Date without time |
|
815
|
+
| `Time/DateTime` | `TIMESTAMP` | Date and time |
|
816
|
+
| `Time` (time-only) | `TIME` | Time without date |
|
817
|
+
| `String` (binary) | `BLOB` | Binary data |
|
818
|
+
| `Array` | `ARRAY` | DuckDB array types |
|
819
|
+
| `Hash` | `JSON` | JSON data type |
|
820
|
+
|
821
|
+
### DuckDB to Ruby Type Mapping
|
822
|
+
|
823
|
+
| DuckDB Type | Ruby Type | Conversion Notes |
|
824
|
+
|-------------|-----------|------------------|
|
825
|
+
| `INTEGER`, `INT4` | `Integer` | 32-bit integer |
|
826
|
+
| `BIGINT`, `INT8` | `Integer` | 64-bit integer |
|
827
|
+
| `SMALLINT`, `INT2` | `Integer` | 16-bit integer |
|
828
|
+
| `TINYINT`, `INT1` | `Integer` | 8-bit integer |
|
829
|
+
| `REAL`, `FLOAT4` | `Float` | Single precision |
|
830
|
+
| `DOUBLE`, `FLOAT8` | `Float` | Double precision |
|
831
|
+
| `DECIMAL`, `NUMERIC` | `BigDecimal` | Exact numeric |
|
832
|
+
| `VARCHAR`, `TEXT` | `String` | Text data |
|
833
|
+
| `BOOLEAN` | `TrueClass/FalseClass` | Boolean values |
|
834
|
+
| `DATE` | `Date` | Date only |
|
835
|
+
| `TIMESTAMP` | `Time` | Date and time |
|
836
|
+
| `TIME` | `Time` | Time only |
|
837
|
+
| `BLOB`, `BYTEA` | `String` | Binary data as string |
|
838
|
+
| `JSON` | `String` | JSON as string (parse manually) |
|
839
|
+
| `ARRAY` | `Array` | Native array support |
|
840
|
+
| `UUID` | `String` | UUID as string |
|
841
|
+
|
842
|
+
### Custom Type Handling
|
843
|
+
|
844
|
+
```ruby
|
845
|
+
# Register custom type conversion
|
846
|
+
db.conversion_procs[DuckDB::Type::UUID] = proc { |value|
|
847
|
+
UUID.parse(value) if value
|
848
|
+
}
|
849
|
+
|
850
|
+
# Handle JSON columns
|
851
|
+
class Product < Sequel::Model
|
852
|
+
def metadata
|
853
|
+
JSON.parse(super) if super
|
854
|
+
end
|
855
|
+
|
856
|
+
def metadata=(value)
|
857
|
+
super(value.to_json)
|
858
|
+
end
|
859
|
+
end
|
860
|
+
```
|
861
|
+
|
862
|
+
## Performance Tuning
|
863
|
+
|
864
|
+
### Query Optimization
|
865
|
+
|
866
|
+
```ruby
|
867
|
+
# Use EXPLAIN to analyze queries
|
868
|
+
puts db[:users].join(:orders, user_id: :id).explain
|
869
|
+
|
870
|
+
# Create appropriate indexes
|
871
|
+
db.add_index :users, :email
|
872
|
+
db.add_index :orders, [:user_id, :status]
|
873
|
+
db.add_index :products, [:category_id, :active]
|
874
|
+
|
875
|
+
# Use partial indexes for filtered queries
|
876
|
+
db.add_index :products, :price, where: { active: true }
|
877
|
+
```
|
878
|
+
|
879
|
+
### Memory Management
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
# Configure memory limits
|
883
|
+
db.run "SET memory_limit='4GB'"
|
884
|
+
db.run "SET max_memory='8GB'"
|
885
|
+
|
886
|
+
# Use streaming for large result sets
|
887
|
+
db[:large_table].paged_each(rows_per_fetch: 1000) do |row|
|
888
|
+
process_row(row)
|
889
|
+
end
|
890
|
+
```
|
891
|
+
|
892
|
+
### Bulk Operations
|
893
|
+
|
894
|
+
```ruby
|
895
|
+
# Efficient bulk insert
|
896
|
+
data = 10000.times.map { |i| {name: "User #{i}", email: "user#{i}@example.com"} }
|
897
|
+
db[:users].multi_insert(data)
|
898
|
+
|
899
|
+
# Batch processing
|
900
|
+
data.each_slice(1000) do |batch|
|
901
|
+
db.transaction do
|
902
|
+
db[:users].multi_insert(batch)
|
903
|
+
end
|
904
|
+
end
|
905
|
+
```
|
906
|
+
|
907
|
+
### Connection Pooling
|
908
|
+
|
909
|
+
```ruby
|
910
|
+
# Optimize connection pool
|
911
|
+
db = Sequel.connect(
|
912
|
+
'duckdb:///database.duckdb',
|
913
|
+
max_connections: 20,
|
914
|
+
pool_timeout: 10,
|
915
|
+
pool_sleep_time: 0.001
|
916
|
+
)
|
917
|
+
```
|
918
|
+
|
919
|
+
This comprehensive API documentation covers all major aspects of using the Sequel DuckDB adapter. For the most up-to-date information, refer to the inline YARD documentation in the source code.
|