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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.kiro/specs/advanced-sql-features-implementation/design.md +24 -0
  3. data/.kiro/specs/advanced-sql-features-implementation/requirements.md +43 -0
  4. data/.kiro/specs/advanced-sql-features-implementation/tasks.md +24 -0
  5. data/.kiro/specs/duckdb-sql-syntax-compatibility/design.md +258 -0
  6. data/.kiro/specs/duckdb-sql-syntax-compatibility/requirements.md +84 -0
  7. data/.kiro/specs/duckdb-sql-syntax-compatibility/tasks.md +94 -0
  8. data/.kiro/specs/edge-cases-and-validation-fixes/requirements.md +32 -0
  9. data/.kiro/specs/integration-test-database-setup/design.md +0 -0
  10. data/.kiro/specs/integration-test-database-setup/requirements.md +117 -0
  11. data/.kiro/specs/sequel-duckdb-adapter/design.md +542 -0
  12. data/.kiro/specs/sequel-duckdb-adapter/requirements.md +202 -0
  13. data/.kiro/specs/sequel-duckdb-adapter/tasks.md +247 -0
  14. data/.kiro/specs/sql-expression-handling-fix/design.md +298 -0
  15. data/.kiro/specs/sql-expression-handling-fix/requirements.md +86 -0
  16. data/.kiro/specs/sql-expression-handling-fix/tasks.md +22 -0
  17. data/.kiro/specs/test-infrastructure-improvements/requirements.md +106 -0
  18. data/.kiro/steering/product.md +22 -0
  19. data/.kiro/steering/structure.md +88 -0
  20. data/.kiro/steering/tech.md +124 -0
  21. data/.kiro/steering/testing.md +192 -0
  22. data/.rubocop.yml +103 -0
  23. data/.yardopts +8 -0
  24. data/API_DOCUMENTATION.md +919 -0
  25. data/CHANGELOG.md +131 -0
  26. data/LICENSE +21 -0
  27. data/MIGRATION_EXAMPLES.md +740 -0
  28. data/PERFORMANCE_OPTIMIZATIONS.md +723 -0
  29. data/README.md +692 -0
  30. data/Rakefile +27 -0
  31. data/TASK_10.2_IMPLEMENTATION_SUMMARY.md +164 -0
  32. data/docs/DUCKDB_SQL_PATTERNS.md +410 -0
  33. data/docs/TASK_12_VERIFICATION_SUMMARY.md +122 -0
  34. data/lib/sequel/adapters/duckdb.rb +256 -0
  35. data/lib/sequel/adapters/shared/duckdb.rb +2349 -0
  36. data/lib/sequel/duckdb/version.rb +16 -0
  37. data/lib/sequel/duckdb.rb +43 -0
  38. data/sig/sequel/duckdb.rbs +6 -0
  39. metadata +235 -0
data/README.md ADDED
@@ -0,0 +1,692 @@
1
+ # Sequel::DuckDB
2
+
3
+ A Ruby database adapter that enables [Sequel](https://sequel.jeremyevans.net/) to work with [DuckDB](https://duckdb.org/) databases. This gem provides full integration between Sequel's powerful ORM and query building capabilities with DuckDB's high-performance analytical database engine.
4
+
5
+ ## Features
6
+
7
+ - **Complete Sequel Integration**: Full compatibility with Sequel's Database and Dataset APIs
8
+ - **Connection Management**: Support for both file-based and in-memory DuckDB databases
9
+ - **Schema Introspection**: Automatic discovery of tables, columns, indexes, and constraints
10
+ - **SQL Generation**: DuckDB-optimized SQL generation for all standard operations
11
+ - **Data Type Mapping**: Seamless conversion between Ruby and DuckDB data types
12
+ - **Transaction Support**: Full transaction handling with commit/rollback capabilities
13
+ - **Error Handling**: Comprehensive error mapping to appropriate Sequel exceptions
14
+ - **Performance Optimized**: Leverages DuckDB's columnar storage and parallel processing
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'sequel-duckdb'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ ```bash
27
+ bundle install
28
+ ```
29
+
30
+ Or install it yourself as:
31
+
32
+ ```bash
33
+ gem install sequel-duckdb
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ### Basic Connection
39
+
40
+ ```ruby
41
+ require 'sequel'
42
+
43
+ # Connect to an in-memory database
44
+ db = Sequel.connect('duckdb::memory:')
45
+
46
+ # Connect to a file database
47
+ db = Sequel.connect('duckdb:///path/to/database.duckdb')
48
+
49
+ # Alternative connection syntax
50
+ db = Sequel.connect(
51
+ adapter: 'duckdb',
52
+ database: '/path/to/database.duckdb'
53
+ )
54
+ ```
55
+
56
+ ### Basic Usage
57
+
58
+ ```ruby
59
+ # Create a table
60
+ db.create_table :users do
61
+ primary_key :id
62
+ String :name, null: false
63
+ String :email
64
+ Integer :age
65
+ DateTime :created_at
66
+ end
67
+
68
+ # Insert data
69
+ users = db[:users]
70
+ users.insert(name: 'John Doe', email: 'john@example.com', age: 30, created_at: Time.now)
71
+ users.insert(name: 'Jane Smith', email: 'jane@example.com', age: 25, created_at: Time.now)
72
+
73
+ # Query data
74
+ puts users.where(age: 30).first
75
+ # => {:id=>1, :name=>"John Doe", :email=>"john@example.com", :age=>30, :created_at=>...}
76
+
77
+ puts users.where { age > 25 }.all
78
+ # => [{:id=>1, :name=>"John Doe", ...}, ...]
79
+
80
+ # Update data
81
+ users.where(name: 'John Doe').update(age: 31)
82
+
83
+ # Delete data
84
+ users.where(age: 25).delete
85
+ ```
86
+
87
+ ## Development
88
+
89
+ After checking out the repo, run `bin/setup` to install dependencies:
90
+
91
+ ```bash
92
+ git clone https://github.com/aguynamedryan/sequel-duckdb.git
93
+ cd sequel-duckdb
94
+ bin/setup
95
+ ```
96
+
97
+ ### Running Tests
98
+
99
+ The test suite uses Minitest and includes both unit tests (using Sequel's mock database) and integration tests (using real DuckDB databases):
100
+
101
+ ```bash
102
+ # Run all tests
103
+ bundle exec rake test
104
+
105
+ # Run specific test file
106
+ ruby test/database_test.rb
107
+
108
+ # Run tests with verbose output
109
+ ruby test/all.rb -v
110
+ ```
111
+
112
+ ### Development Console
113
+
114
+ You can run `bin/console` for an interactive prompt that will allow you to experiment:
115
+
116
+ ```bash
117
+ bin/console
118
+ ```
119
+
120
+ This will start an IRB session with the gem loaded and a test database available.
121
+
122
+ ### Code Quality
123
+
124
+ The project uses RuboCop for code style enforcement:
125
+
126
+ ```bash
127
+ # Check code style
128
+ bundle exec rubocop
129
+
130
+ # Auto-fix style issues
131
+ bundle exec rubocop -a
132
+ ```
133
+
134
+ ### Building and Installing
135
+
136
+ To install this gem onto your local machine:
137
+
138
+ ```bash
139
+ bundle exec rake install
140
+ ```
141
+
142
+ To build the gem:
143
+
144
+ ```bash
145
+ bundle exec rake build
146
+ ```
147
+
148
+ ### Release Process
149
+
150
+ To release a new version:
151
+
152
+ 1. Update the version number in `lib/sequel/duckdb/version.rb`
153
+ 2. Update `CHANGELOG.md` with the new version details
154
+ 3. Run the tests to ensure everything works
155
+ 4. Commit the changes
156
+ 5. Run `bundle exec rake release`
157
+
158
+ This will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
159
+
160
+ ## Contributing
161
+
162
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aguynamedryan/sequel-duckdb.
163
+
164
+ ### Development Guidelines
165
+
166
+ 1. **Follow TDD**: Write tests before implementing features
167
+ 2. **Code Style**: Follow the existing RuboCop configuration
168
+ 3. **Documentation**: Update README and code documentation for new features
169
+ 4. **Compatibility**: Ensure compatibility with supported Ruby and Sequel versions
170
+ 5. **Performance**: Consider performance implications, especially for analytical workloads
171
+
172
+ ### Reporting Issues
173
+
174
+ When reporting issues, please include:
175
+
176
+ - Ruby version
177
+ - Sequel version
178
+ - DuckDB version
179
+ - Operating system
180
+ - Minimal code example that reproduces the issue
181
+ - Full error message and stack trace
182
+
183
+ ## License
184
+
185
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
186
+
187
+ ## Acknowledgments
188
+
189
+ - [Jeremy Evans](https://github.com/jeremyevans) for creating and maintaining Sequel
190
+ - The [DuckDB team](https://duckdb.org/docs/api/ruby) for the excellent database engine and Ruby client
191
+ - Contributors to [sequel-hexspace](https://github.com/hexspace/sequel-hexspace) and other Sequel adapters for implementation patterns
192
+ ## Connection Options
193
+
194
+ ### Connection Strings
195
+
196
+ ```ruby
197
+ # In-memory database (data lost when connection closes)
198
+ db = Sequel.connect('duckdb::memory:')
199
+
200
+ # File database (persistent storage)
201
+ db = Sequel.connect('duckdb:///absolute/path/to/database.duckdb')
202
+ db = Sequel.connect('duckdb://relative/path/to/database.duckdb')
203
+
204
+ # With connection options
205
+ db = Sequel.connect('duckdb:///path/to/database.duckdb?readonly=true')
206
+ ```
207
+
208
+ ### Connection Hash
209
+
210
+ ```ruby
211
+ db = Sequel.connect(
212
+ adapter: 'duckdb',
213
+ database: '/path/to/database.duckdb',
214
+ # DuckDB-specific options
215
+ readonly: false,
216
+ config: {
217
+ threads: 4,
218
+ memory_limit: '1GB'
219
+ }
220
+ )
221
+ ```
222
+
223
+ ## Schema Operations
224
+
225
+ ### Table Management
226
+
227
+ ```ruby
228
+ # Create table with various column types
229
+ db.create_table :products do
230
+ primary_key :id
231
+ String :name, size: 255, null: false
232
+ Text :description
233
+ Decimal :price, size: [10, 2]
234
+ Integer :stock_quantity
235
+ Boolean :active, default: true
236
+ Date :release_date
237
+ DateTime :created_at
238
+ Time :daily_update_time
239
+ column :metadata, 'JSON' # DuckDB-specific type
240
+ end
241
+
242
+ # Add columns
243
+ db.alter_table :products do
244
+ add_column :category_id, Integer
245
+ add_index :category_id
246
+ end
247
+
248
+ # Drop table
249
+ db.drop_table :products
250
+ ```
251
+
252
+ ### Schema Introspection
253
+
254
+ ```ruby
255
+ # List all tables
256
+ db.tables
257
+ # => [:users, :products, :orders]
258
+
259
+ # Get table schema
260
+ db.schema(:users)
261
+ # => [[:id, {:type=>:integer, :db_type=>"INTEGER", :primary_key=>true, ...}],
262
+ # [:name, {:type=>:string, :db_type=>"VARCHAR", :allow_null=>false, ...}], ...]
263
+
264
+ # Check if table exists
265
+ db.table_exists?(:users)
266
+ # => true
267
+
268
+ # Get indexes
269
+ db.indexes(:users)
270
+ # => {:users_name_index => {:columns=>[:name], :unique=>false, :primary=>false}}
271
+ ```
272
+
273
+ ## Data Types
274
+
275
+ ### Supported Type Mappings
276
+
277
+ | Ruby Type | DuckDB Type | Notes |
278
+ | -------------------- | --------------- | ----------------------------- |
279
+ | String | VARCHAR/TEXT | Configurable size |
280
+ | Integer | INTEGER/BIGINT | Auto-sized based on value |
281
+ | Float | REAL/DOUBLE | Precision preserved |
282
+ | BigDecimal | DECIMAL/NUMERIC | Precision and scale supported |
283
+ | TrueClass/FalseClass | BOOLEAN | Native boolean support |
284
+ | Date | DATE | Date-only values |
285
+ | Time/DateTime | TIMESTAMP | Full datetime with timezone |
286
+ | Time (time-only) | TIME | Time-only values |
287
+ | String (binary) | BLOB | Binary data storage |
288
+
289
+ ### Type Conversion Examples
290
+
291
+ ```ruby
292
+ # Automatic type conversion
293
+ users.insert(
294
+ name: 'Alice', # String -> VARCHAR
295
+ age: 28, # Integer -> INTEGER
296
+ salary: BigDecimal('75000.50'), # BigDecimal -> DECIMAL
297
+ active: true, # Boolean -> BOOLEAN
298
+ birth_date: Date.new(1995, 5, 15), # Date -> DATE
299
+ created_at: Time.now, # Time -> TIMESTAMP
300
+ profile_data: '{"key": "value"}' # String -> JSON (if column defined as JSON)
301
+ )
302
+
303
+ # Retrieved data is automatically converted back to Ruby types
304
+ user = users.first
305
+ user[:birth_date].class # => Date
306
+ user[:created_at].class # => Time
307
+ user[:active].class # => TrueClass
308
+ ```
309
+
310
+ ## Query Building
311
+
312
+ ### Basic Queries
313
+
314
+ ```ruby
315
+ users = db[:users]
316
+
317
+ # SELECT with conditions
318
+ users.where(active: true)
319
+ users.where { age > 25 }
320
+ users.where(Sequel.like(:name, 'John%'))
321
+
322
+ # Ordering and limiting
323
+ users.order(:name).limit(10)
324
+ users.order(Sequel.desc(:created_at)).first
325
+
326
+ # Aggregation
327
+ users.count
328
+ users.avg(:age)
329
+ users.group(:department).select(:department, Sequel.count(:id).as(:user_count))
330
+ ```
331
+
332
+ ### Advanced Queries
333
+
334
+ ```ruby
335
+ # JOINs
336
+ db[:users]
337
+ .join(:orders, user_id: :id)
338
+ .select(:users__name, :orders__total)
339
+ .where { orders__total > 100 }
340
+
341
+ # Subqueries
342
+ high_value_users = db[:orders]
343
+ .group(:user_id)
344
+ .having { sum(:total) > 1000 }
345
+ .select(:user_id)
346
+
347
+ db[:users].where(id: high_value_users)
348
+
349
+ # Window functions (DuckDB-specific optimization)
350
+ db[:sales]
351
+ .select(
352
+ :product_id,
353
+ :amount,
354
+ Sequel.function(:row_number).over(partition: :product_id, order: :amount).as(:rank)
355
+ )
356
+
357
+ # Common Table Expressions (CTEs)
358
+ db.with(:high_spenders,
359
+ db[:orders].group(:user_id).having { sum(:total) > 1000 }.select(:user_id)
360
+ ).from(:high_spenders)
361
+ .join(:users, id: :user_id)
362
+ .select(:users__name)
363
+ ```
364
+
365
+ ## Transactions
366
+
367
+ ### Basic Transactions
368
+
369
+ ```ruby
370
+ db.transaction do
371
+ users.insert(name: 'Alice', email: 'alice@example.com')
372
+ orders.insert(user_id: users.max(:id), total: 100.00)
373
+ # Automatically commits if no exceptions
374
+ end
375
+
376
+ # Manual rollback
377
+ db.transaction do
378
+ users.insert(name: 'Bob', email: 'bob@example.com')
379
+ raise Sequel::Rollback if some_condition
380
+ # Transaction will be rolled back
381
+ end
382
+ ```
383
+
384
+ ### Error Handling
385
+
386
+ ```ruby
387
+ begin
388
+ db.transaction do
389
+ # Some database operations
390
+ users.insert(name: nil) # This will fail due to NOT NULL constraint
391
+ end
392
+ rescue Sequel::NotNullConstraintViolation => e
393
+ puts "Cannot insert user with null name: #{e.message}"
394
+ rescue Sequel::DatabaseError => e
395
+ puts "Database error: #{e.message}"
396
+ end
397
+ ```
398
+
399
+ ## DuckDB-Specific Features
400
+
401
+ ### Analytical Queries
402
+
403
+ ```ruby
404
+ # DuckDB excels at analytical workloads
405
+ sales_summary = db[:sales]
406
+ .select(
407
+ :product_category,
408
+ Sequel.function(:sum, :amount).as(:total_sales),
409
+ Sequel.function(:avg, :amount).as(:avg_sale),
410
+ Sequel.function(:count, :id).as(:transaction_count)
411
+ )
412
+ .group(:product_category)
413
+ .order(Sequel.desc(:total_sales))
414
+
415
+ # Window functions for analytics
416
+ monthly_trends = db[:sales]
417
+ .select(
418
+ :month,
419
+ :amount,
420
+ Sequel.function(:lag, :amount, 1).over(order: :month).as(:prev_month),
421
+ Sequel.function(:sum, :amount).over(order: :month).as(:running_total)
422
+ )
423
+ ```
424
+
425
+ ### Performance Optimizations
426
+
427
+ ```ruby
428
+ # Bulk inserts (more efficient than individual inserts)
429
+ users_data = [
430
+ {name: 'User 1', email: 'user1@example.com'},
431
+ {name: 'User 2', email: 'user2@example.com'},
432
+ # ... many more records
433
+ ]
434
+
435
+ db[:users].multi_insert(users_data)
436
+
437
+ # Use DuckDB's columnar storage advantages
438
+ # Query only needed columns for better performance
439
+ db[:large_table].select(:id, :name, :created_at).where(active: true)
440
+ ```
441
+
442
+ ## Model Integration
443
+
444
+ ### Sequel::Model Usage
445
+
446
+ ```ruby
447
+ class User < Sequel::Model
448
+ # Sequel automatically introspects the users table schema
449
+
450
+ # Associations work normally
451
+ one_to_many :orders
452
+
453
+ # Validations
454
+ def validate
455
+ super
456
+ errors.add(:email, 'must be present') if !email || email.empty?
457
+ errors.add(:email, 'must be valid') unless email =~ /@/
458
+ end
459
+
460
+ # Custom methods
461
+ def full_name
462
+ "#{first_name} #{last_name}"
463
+ end
464
+ end
465
+
466
+ class Order < Sequel::Model
467
+ many_to_one :user
468
+
469
+ def total_with_tax(tax_rate = 0.08)
470
+ total * (1 + tax_rate)
471
+ end
472
+ end
473
+
474
+ # Usage
475
+ user = User.create(name: 'John Doe', email: 'john@example.com')
476
+ order = user.add_order(total: 99.99, status: 'pending')
477
+
478
+ # Associations work seamlessly
479
+ user.orders.where(status: 'completed').sum(:total)
480
+ ```
481
+
482
+ ## Error Handling
483
+
484
+ The adapter maps DuckDB errors to appropriate Sequel exception types:
485
+
486
+ ```ruby
487
+ begin
488
+ # Various operations that might fail
489
+ db[:users].insert(name: nil) # NOT NULL violation
490
+ rescue Sequel::NotNullConstraintViolation => e
491
+ # Handle null constraint violation
492
+ rescue Sequel::UniqueConstraintViolation => e
493
+ # Handle unique constraint violation
494
+ rescue Sequel::ForeignKeyConstraintViolation => e
495
+ # Handle foreign key violation
496
+ rescue Sequel::CheckConstraintViolation => e
497
+ # Handle check constraint violation
498
+ rescue Sequel::DatabaseConnectionError => e
499
+ # Handle connection issues
500
+ rescue Sequel::DatabaseError => e
501
+ # Handle other database errors
502
+ end
503
+ ```
504
+
505
+ ## Troubleshooting
506
+
507
+ ### Common Issues
508
+
509
+ #### Connection Problems
510
+
511
+ ```ruby
512
+ # Issue: Cannot connect to database file
513
+ # Solution: Check file path and permissions
514
+ begin
515
+ db = Sequel.connect('duckdb:///path/to/database.duckdb')
516
+ rescue Sequel::DatabaseConnectionError => e
517
+ puts "Connection failed: #{e.message}"
518
+ # Check if directory exists and is writable
519
+ # Ensure DuckDB gem is properly installed
520
+ end
521
+ ```
522
+
523
+ #### Memory Issues
524
+
525
+ ```ruby
526
+ # Issue: Out of memory with large datasets
527
+ # Solution: Use streaming or limit result sets
528
+ db[:large_table].limit(1000).each do |row|
529
+ # Process row by row instead of loading all at once
530
+ end
531
+
532
+ # Or use DuckDB's memory configuration
533
+ db = Sequel.connect(
534
+ adapter: 'duckdb',
535
+ database: ':memory:',
536
+ config: { memory_limit: '2GB' }
537
+ )
538
+ ```
539
+
540
+ #### Performance Issues
541
+
542
+ ```ruby
543
+ # Issue: Slow queries
544
+ # Solution: Add appropriate indexes
545
+ db.add_index :users, :email
546
+ db.add_index :orders, [:user_id, :created_at]
547
+
548
+ # Use EXPLAIN to analyze query plans
549
+ puts db[:users].where(email: 'john@example.com').explain
550
+ ```
551
+
552
+ ### Debugging
553
+
554
+ ```ruby
555
+ # Enable SQL logging to see generated queries
556
+ require 'logger'
557
+ db.loggers << Logger.new($stdout)
558
+
559
+ # This will now log all SQL queries
560
+ db[:users].where(active: true).all
561
+ # Logs: SELECT * FROM users WHERE (active = true)
562
+ ```
563
+
564
+ ### Version Compatibility
565
+
566
+ - **Ruby**: 3.1.0 or higher
567
+ - **Sequel**: 5.0 or higher
568
+ - **DuckDB**: 0.8.0 or higher
569
+ - **ruby-duckdb**: 1.0.0 or higher
570
+
571
+ ### Getting Help
572
+
573
+ - **Documentation**: [Sequel Documentation](https://sequel.jeremyevans.net/documentation.html)
574
+ - **DuckDB Docs**: [DuckDB Documentation](https://duckdb.org/docs/)
575
+ - **Issues**: Report bugs on [GitHub Issues](https://github.com/aguynamedryan/sequel-duckdb/issues)
576
+ - **Discussions**: Join discussions on [GitHub Discussions](https://github.com/aguynamedryan/sequel-duckdb/discussions)
577
+
578
+ ## Performance Tips
579
+
580
+ ### Query Optimization
581
+
582
+ 1. **Select only needed columns**: DuckDB's columnar storage makes this very efficient
583
+ ```ruby
584
+ # Good
585
+ db[:users].select(:id, :name).where(active: true)
586
+
587
+ # Less efficient
588
+ db[:users].where(active: true) # Selects all columns
589
+ ```
590
+
591
+ 2. **Use appropriate indexes**: Especially for frequently queried columns
592
+ ```ruby
593
+ db.add_index :users, :email
594
+ db.add_index :orders, [:user_id, :status]
595
+ ```
596
+
597
+ 3. **Leverage DuckDB's analytical capabilities**: Use window functions and aggregations
598
+ ```ruby
599
+ # Efficient analytical query
600
+ db[:sales]
601
+ .select(
602
+ :product_id,
603
+ Sequel.function(:sum, :amount).as(:total),
604
+ Sequel.function(:rank).over(order: Sequel.desc(:amount)).as(:rank)
605
+ )
606
+ .group(:product_id)
607
+ ```
608
+
609
+ ### Memory Management
610
+
611
+ 1. **Use streaming for large result sets**:
612
+ ```ruby
613
+ db[:large_table].paged_each(rows_per_fetch: 1000) do |row|
614
+ # Process row by row
615
+ end
616
+ ```
617
+
618
+ 2. **Configure DuckDB memory limits**:
619
+ ```ruby
620
+ db = Sequel.connect(
621
+ adapter: 'duckdb',
622
+ database: '/path/to/db.duckdb',
623
+ config: {
624
+ memory_limit: '4GB',
625
+ threads: 8
626
+ }
627
+ )
628
+ ```
629
+
630
+ ### Bulk Operations
631
+
632
+ 1. **Use multi_insert for bulk data loading**:
633
+ ```ruby
634
+ # Efficient bulk insert
635
+ data = 1000.times.map { |i| {name: "User #{i}", email: "user#{i}@example.com"} }
636
+ db[:users].multi_insert(data)
637
+ ```
638
+
639
+ 2. **Use transactions for multiple operations**:
640
+ ```ruby
641
+ db.transaction do
642
+ # Multiple related operations
643
+ user_id = db[:users].insert(name: 'John', email: 'john@example.com')
644
+ db[:profiles].insert(user_id: user_id, bio: 'Software developer')
645
+ db[:preferences].insert(user_id: user_id, theme: 'dark')
646
+ end
647
+ ```
648
+
649
+ ## Documentation
650
+
651
+ ### Complete API Reference
652
+
653
+ For comprehensive API documentation including all methods, configuration options, and advanced features, see:
654
+
655
+ - **[API_DOCUMENTATION.md](API_DOCUMENTATION.md)** - Complete API reference with examples
656
+ - **[docs/DUCKDB_SQL_PATTERNS.md](docs/DUCKDB_SQL_PATTERNS.md)** - Detailed SQL generation patterns and syntax
657
+
658
+ ### SQL Generation Patterns
659
+
660
+ The sequel-duckdb adapter generates SQL optimized for DuckDB while maintaining Sequel compatibility. Key patterns include:
661
+
662
+ - **LIKE clauses**: Clean syntax without ESCAPE clauses
663
+ - **ILIKE support**: Converted to UPPER() LIKE UPPER() for case-insensitive matching
664
+ - **Regular expressions**: Using DuckDB's regexp_matches() function
665
+ - **Qualified columns**: Standard dot notation (table.column)
666
+ - **Recursive CTEs**: Automatic WITH RECURSIVE detection
667
+ - **Proper parentheses**: Consistent expression grouping
668
+
669
+ Example SQL patterns:
670
+ ```ruby
671
+ # LIKE patterns
672
+ dataset.where(Sequel.like(:name, "%John%"))
673
+ # SQL: SELECT * FROM users WHERE (name LIKE '%John%')
674
+
675
+ # ILIKE patterns (case-insensitive)
676
+ dataset.where(Sequel.ilike(:name, "%john%"))
677
+ # SQL: SELECT * FROM users WHERE (UPPER(name) LIKE UPPER('%john%'))
678
+
679
+ # Regular expressions
680
+ dataset.where(name: /^John/)
681
+ # SQL: SELECT * FROM users WHERE (regexp_matches(name, '^John'))
682
+ ```
683
+
684
+ For complete SQL pattern documentation, see [docs/DUCKDB_SQL_PATTERNS.md](docs/DUCKDB_SQL_PATTERNS.md).
685
+
686
+ ## Contributing
687
+
688
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aguynamedryan/sequel-duckdb.
689
+
690
+ ## License
691
+
692
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ require "rake/testtask"
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ # Exclude performance tests by default
12
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/performance*_test.rb")
13
+ end
14
+
15
+ # Create a separate task for performance tests
16
+ Rake::TestTask.new(:test_performance) do |t|
17
+ t.libs << "test"
18
+ t.test_files = FileList["test/performance*_test.rb"]
19
+ end
20
+
21
+ # Task to run all tests including performance
22
+ Rake::TestTask.new(:test_all) do |t|
23
+ t.libs << "test"
24
+ t.test_files = FileList["test/**/*_test.rb"]
25
+ end
26
+
27
+ task default: %i[test rubocop]