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
@@ -0,0 +1,740 @@
1
+ # Sequel Migration Examples for DuckDB
2
+
3
+ This document provides comprehensive examples of using Sequel migrations with DuckDB, covering common patterns, best practices, and DuckDB-specific considerations.
4
+
5
+ ## Basic Migration Structure
6
+
7
+ ### Creating a Migration
8
+
9
+ ```ruby
10
+ # db/migrate/001_create_users.rb
11
+ Sequel.migration do
12
+ up do
13
+ create_table(:users) do
14
+ primary_key :id
15
+ String :name, null: false, size: 255
16
+ String :email, unique: true, null: false
17
+ Integer :age
18
+ Boolean :active, default: true
19
+ DateTime :created_at, null: false
20
+ DateTime :updated_at, null: false
21
+ end
22
+ end
23
+
24
+ down do
25
+ drop_table(:users)
26
+ end
27
+ end
28
+ ```
29
+
30
+ ### Running Migrations
31
+
32
+ ```ruby
33
+ # Setup database connection
34
+ require 'sequel'
35
+ db = Sequel.connect('duckdb:///path/to/database.duckdb')
36
+
37
+ # Run migrations
38
+ Sequel::Migrator.run(db, 'db/migrate')
39
+
40
+ # Run migrations to specific version
41
+ Sequel::Migrator.run(db, 'db/migrate', target: 5)
42
+
43
+ # Check current migration version
44
+ puts "Current version: #{db[:schema_info].first[:version]}"
45
+ ```
46
+
47
+ ## Table Operations
48
+
49
+ ### Creating Tables with Various Column Types
50
+
51
+ ```ruby
52
+ # db/migrate/002_create_products.rb
53
+ Sequel.migration do
54
+ up do
55
+ create_table(:products) do
56
+ primary_key :id
57
+
58
+ # String types
59
+ String :name, null: false, size: 255
60
+ String :sku, size: 50, unique: true
61
+ Text :description
62
+
63
+ # Numeric types
64
+ Integer :stock_quantity, default: 0
65
+ Decimal :price, size: [10, 2], null: false
66
+ Float :weight
67
+
68
+ # Date/time types
69
+ Date :release_date
70
+ DateTime :created_at, null: false
71
+ DateTime :updated_at, null: false
72
+ Time :daily_update_time
73
+
74
+ # Boolean
75
+ Boolean :active, default: true
76
+ Boolean :featured, default: false
77
+
78
+ # JSON (DuckDB-specific)
79
+ column :metadata, 'JSON'
80
+ column :tags, 'VARCHAR[]' # Array type
81
+
82
+ # Constraints
83
+ constraint(:positive_price) { price > 0 }
84
+ constraint(:valid_stock) { stock_quantity >= 0 }
85
+ end
86
+ end
87
+
88
+ down do
89
+ drop_table(:products)
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Altering Tables
95
+
96
+ ```ruby
97
+ # db/migrate/003_add_category_to_products.rb
98
+ Sequel.migration do
99
+ up do
100
+ alter_table(:products) do
101
+ add_column :category_id, Integer
102
+ add_column :brand, String, size: 100
103
+ add_column :discontinued_at, DateTime
104
+
105
+ # Add foreign key constraint
106
+ add_foreign_key [:category_id], :categories, key: [:id]
107
+
108
+ # Add index
109
+ add_index :category_id
110
+ add_index [:brand, :active]
111
+ end
112
+ end
113
+
114
+ down do
115
+ alter_table(:products) do
116
+ drop_foreign_key [:category_id]
117
+ drop_index [:brand, :active]
118
+ drop_index :category_id
119
+ drop_column :discontinued_at
120
+ drop_column :brand
121
+ drop_column :category_id
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### Modifying Columns
128
+
129
+ ```ruby
130
+ # db/migrate/004_modify_user_columns.rb
131
+ Sequel.migration do
132
+ up do
133
+ alter_table(:users) do
134
+ # Change column type
135
+ set_column_type :age, Integer
136
+
137
+ # Change column default
138
+ set_column_default :active, false
139
+
140
+ # Add/remove null constraint
141
+ set_column_allow_null :email, false
142
+
143
+ # Rename column
144
+ rename_column :name, :full_name
145
+ end
146
+ end
147
+
148
+ down do
149
+ alter_table(:users) do
150
+ rename_column :full_name, :name
151
+ set_column_allow_null :email, true
152
+ set_column_default :active, true
153
+ set_column_type :age, String
154
+ end
155
+ end
156
+ end
157
+ ```
158
+
159
+ ## Index Management
160
+
161
+ ### Creating Indexes
162
+
163
+ ```ruby
164
+ # db/migrate/005_add_indexes.rb
165
+ Sequel.migration do
166
+ up do
167
+ # Single column index
168
+ add_index :users, :email
169
+
170
+ # Multi-column index
171
+ add_index :products, [:category_id, :active]
172
+
173
+ # Unique index
174
+ add_index :products, :sku, unique: true
175
+
176
+ # Named index
177
+ add_index :users, :created_at, name: :idx_users_created_at
178
+
179
+ # Partial index (with WHERE clause)
180
+ add_index :products, :name, where: { active: true }
181
+ end
182
+
183
+ down do
184
+ drop_index :products, :name, where: { active: true }
185
+ drop_index :users, :created_at, name: :idx_users_created_at
186
+ drop_index :products, :sku, unique: true
187
+ drop_index :products, [:category_id, :active]
188
+ drop_index :users, :email
189
+ end
190
+ end
191
+ ```
192
+
193
+ ## Constraints and Relationships
194
+
195
+ ### Foreign Key Constraints
196
+
197
+ ```ruby
198
+ # db/migrate/006_create_orders_with_relationships.rb
199
+ Sequel.migration do
200
+ up do
201
+ create_table(:categories) do
202
+ primary_key :id
203
+ String :name, null: false, unique: true
204
+ String :description
205
+ DateTime :created_at, null: false
206
+ end
207
+
208
+ create_table(:orders) do
209
+ primary_key :id
210
+ foreign_key :user_id, :users, null: false, on_delete: :cascade
211
+
212
+ Decimal :total, size: [10, 2], null: false
213
+ String :status, default: 'pending'
214
+ DateTime :created_at, null: false
215
+ DateTime :updated_at, null: false
216
+
217
+ # Composite foreign key example
218
+ # foreign_key [:user_id, :product_id], :user_products
219
+ end
220
+
221
+ create_table(:order_items) do
222
+ primary_key :id
223
+ foreign_key :order_id, :orders, null: false, on_delete: :cascade
224
+ foreign_key :product_id, :products, null: false
225
+
226
+ Integer :quantity, null: false, default: 1
227
+ Decimal :unit_price, size: [10, 2], null: false
228
+
229
+ # Ensure positive values
230
+ constraint(:positive_quantity) { quantity > 0 }
231
+ constraint(:positive_price) { unit_price > 0 }
232
+ end
233
+ end
234
+
235
+ down do
236
+ drop_table(:order_items)
237
+ drop_table(:orders)
238
+ drop_table(:categories)
239
+ end
240
+ end
241
+ ```
242
+
243
+ ### Check Constraints
244
+
245
+ ```ruby
246
+ # db/migrate/007_add_check_constraints.rb
247
+ Sequel.migration do
248
+ up do
249
+ alter_table(:users) do
250
+ # Email format validation
251
+ add_constraint(:valid_email) { email.like('%@%') }
252
+
253
+ # Age range validation
254
+ add_constraint(:valid_age) { (age >= 0) & (age <= 150) }
255
+ end
256
+
257
+ alter_table(:products) do
258
+ # Price must be positive
259
+ add_constraint(:positive_price) { price > 0 }
260
+
261
+ # Stock quantity must be non-negative
262
+ add_constraint(:non_negative_stock) { stock_quantity >= 0 }
263
+
264
+ # SKU format validation (example pattern)
265
+ add_constraint(:valid_sku_format) { sku.like('SKU-%') }
266
+ end
267
+ end
268
+
269
+ down do
270
+ alter_table(:products) do
271
+ drop_constraint(:valid_sku_format)
272
+ drop_constraint(:non_negative_stock)
273
+ drop_constraint(:positive_price)
274
+ end
275
+
276
+ alter_table(:users) do
277
+ drop_constraint(:valid_age)
278
+ drop_constraint(:valid_email)
279
+ end
280
+ end
281
+ end
282
+ ```
283
+
284
+ ## DuckDB-Specific Features
285
+
286
+ ### JSON and Array Columns
287
+
288
+ ```ruby
289
+ # db/migrate/008_add_json_and_array_columns.rb
290
+ Sequel.migration do
291
+ up do
292
+ alter_table(:products) do
293
+ # JSON column for flexible metadata
294
+ add_column :specifications, 'JSON'
295
+
296
+ # Array columns
297
+ add_column :tags, 'VARCHAR[]'
298
+ add_column :category_path, 'INTEGER[]'
299
+
300
+ # Map column (key-value pairs)
301
+ add_column :attributes, 'MAP(VARCHAR, VARCHAR)'
302
+ end
303
+
304
+ # Example of populating JSON data
305
+ run <<~SQL
306
+ UPDATE products
307
+ SET specifications = '{"weight": "1.5kg", "dimensions": "10x20x5cm"}'
308
+ WHERE specifications IS NULL
309
+ SQL
310
+
311
+ # Example of populating array data
312
+ run <<~SQL
313
+ UPDATE products
314
+ SET tags = ARRAY['electronics', 'gadget']
315
+ WHERE tags IS NULL
316
+ SQL
317
+ end
318
+
319
+ down do
320
+ alter_table(:products) do
321
+ drop_column :attributes
322
+ drop_column :category_path
323
+ drop_column :tags
324
+ drop_column :specifications
325
+ end
326
+ end
327
+ end
328
+ ```
329
+
330
+ ### Views and Materialized Views
331
+
332
+ ```ruby
333
+ # db/migrate/009_create_views.rb
334
+ Sequel.migration do
335
+ up do
336
+ # Create a view for active products with category info
337
+ run <<~SQL
338
+ CREATE VIEW active_products_view AS
339
+ SELECT
340
+ p.id,
341
+ p.name,
342
+ p.price,
343
+ p.stock_quantity,
344
+ c.name as category_name
345
+ FROM products p
346
+ LEFT JOIN categories c ON p.category_id = c.id
347
+ WHERE p.active = true
348
+ SQL
349
+
350
+ # Create a view for order summaries
351
+ run <<~SQL
352
+ CREATE VIEW order_summaries AS
353
+ SELECT
354
+ o.id as order_id,
355
+ u.full_name as customer_name,
356
+ o.total,
357
+ o.status,
358
+ COUNT(oi.id) as item_count,
359
+ o.created_at
360
+ FROM orders o
361
+ JOIN users u ON o.user_id = u.id
362
+ LEFT JOIN order_items oi ON o.id = oi.order_id
363
+ GROUP BY o.id, u.full_name, o.total, o.status, o.created_at
364
+ SQL
365
+ end
366
+
367
+ down do
368
+ run "DROP VIEW IF EXISTS order_summaries"
369
+ run "DROP VIEW IF EXISTS active_products_view"
370
+ end
371
+ end
372
+ ```
373
+
374
+ ### Sequences (Alternative to Auto-increment)
375
+
376
+ ```ruby
377
+ # db/migrate/010_create_sequences.rb
378
+ Sequel.migration do
379
+ up do
380
+ # Create custom sequence for order numbers
381
+ run "CREATE SEQUENCE order_number_seq START 1000"
382
+
383
+ alter_table(:orders) do
384
+ add_column :order_number, String, unique: true
385
+ end
386
+
387
+ # Set default to use sequence
388
+ run <<~SQL
389
+ UPDATE orders
390
+ SET order_number = 'ORD-' || LPAD(nextval('order_number_seq')::TEXT, 6, '0')
391
+ WHERE order_number IS NULL
392
+ SQL
393
+ end
394
+
395
+ down do
396
+ alter_table(:orders) do
397
+ drop_column :order_number
398
+ end
399
+
400
+ run "DROP SEQUENCE IF EXISTS order_number_seq"
401
+ end
402
+ end
403
+ ```
404
+
405
+ ## Data Migration Patterns
406
+
407
+ ### Populating Initial Data
408
+
409
+ ```ruby
410
+ # db/migrate/011_populate_initial_data.rb
411
+ Sequel.migration do
412
+ up do
413
+ # Insert default categories
414
+ categories_data = [
415
+ { name: 'Electronics', description: 'Electronic devices and gadgets' },
416
+ { name: 'Books', description: 'Books and publications' },
417
+ { name: 'Clothing', description: 'Apparel and accessories' }
418
+ ]
419
+
420
+ categories_data.each do |category|
421
+ run <<~SQL
422
+ INSERT INTO categories (name, description, created_at)
423
+ VALUES ('#{category[:name]}', '#{category[:description]}', NOW())
424
+ SQL
425
+ end
426
+
427
+ # Or use Sequel's dataset methods
428
+ # self[:categories].multi_insert(categories_data.map { |c| c.merge(created_at: Time.now) })
429
+ end
430
+
431
+ down do
432
+ run "DELETE FROM categories WHERE name IN ('Electronics', 'Books', 'Clothing')"
433
+ end
434
+ end
435
+ ```
436
+
437
+ ### Data Transformation
438
+
439
+ ```ruby
440
+ # db/migrate/012_transform_user_data.rb
441
+ Sequel.migration do
442
+ up do
443
+ # Split full_name into first_name and last_name
444
+ alter_table(:users) do
445
+ add_column :first_name, String, size: 100
446
+ add_column :last_name, String, size: 100
447
+ end
448
+
449
+ # Transform existing data
450
+ run <<~SQL
451
+ UPDATE users
452
+ SET
453
+ first_name = SPLIT_PART(full_name, ' ', 1),
454
+ last_name = CASE
455
+ WHEN ARRAY_LENGTH(STRING_SPLIT(full_name, ' ')) > 1
456
+ THEN ARRAY_TO_STRING(ARRAY_SLICE(STRING_SPLIT(full_name, ' '), 2, NULL), ' ')
457
+ ELSE ''
458
+ END
459
+ WHERE full_name IS NOT NULL
460
+ SQL
461
+ end
462
+
463
+ down do
464
+ alter_table(:users) do
465
+ drop_column :last_name
466
+ drop_column :first_name
467
+ end
468
+ end
469
+ end
470
+ ```
471
+
472
+ ## Performance Optimization Migrations
473
+
474
+ ### Adding Indexes for Query Performance
475
+
476
+ ```ruby
477
+ # db/migrate/013_optimize_query_performance.rb
478
+ Sequel.migration do
479
+ up do
480
+ # Index for common WHERE clauses
481
+ add_index :orders, [:status, :created_at]
482
+ add_index :products, [:active, :category_id, :price]
483
+
484
+ # Index for JOIN operations
485
+ add_index :order_items, [:order_id, :product_id]
486
+
487
+ # Index for sorting operations
488
+ add_index :users, [:created_at, :id] # Composite for pagination
489
+
490
+ # Partial indexes for common filtered queries
491
+ add_index :products, :price, where: { active: true }
492
+ add_index :orders, :created_at, where: { status: 'completed' }
493
+ end
494
+
495
+ down do
496
+ drop_index :orders, :created_at, where: { status: 'completed' }
497
+ drop_index :products, :price, where: { active: true }
498
+ drop_index :users, [:created_at, :id]
499
+ drop_index :order_items, [:order_id, :product_id]
500
+ drop_index :products, [:active, :category_id, :price]
501
+ drop_index :orders, [:status, :created_at]
502
+ end
503
+ end
504
+ ```
505
+
506
+ ## Migration Best Practices
507
+
508
+ ### 1. Reversible Migrations
509
+
510
+ Always provide both `up` and `down` methods:
511
+
512
+ ```ruby
513
+ Sequel.migration do
514
+ up do
515
+ # Forward migration
516
+ create_table(:example) do
517
+ primary_key :id
518
+ String :name
519
+ end
520
+ end
521
+
522
+ down do
523
+ # Reverse migration
524
+ drop_table(:example)
525
+ end
526
+ end
527
+ ```
528
+
529
+ ### 2. Safe Column Additions
530
+
531
+ When adding columns with NOT NULL constraints:
532
+
533
+ ```ruby
534
+ Sequel.migration do
535
+ up do
536
+ # Step 1: Add column as nullable
537
+ alter_table(:users) do
538
+ add_column :phone, String
539
+ end
540
+
541
+ # Step 2: Populate with default values
542
+ run "UPDATE users SET phone = 'N/A' WHERE phone IS NULL"
543
+
544
+ # Step 3: Make it NOT NULL
545
+ alter_table(:users) do
546
+ set_column_allow_null :phone, false
547
+ end
548
+ end
549
+
550
+ down do
551
+ alter_table(:users) do
552
+ drop_column :phone
553
+ end
554
+ end
555
+ end
556
+ ```
557
+
558
+ ### 3. Large Data Migrations
559
+
560
+ For large datasets, use batched operations:
561
+
562
+ ```ruby
563
+ Sequel.migration do
564
+ up do
565
+ # Process in batches to avoid memory issues
566
+ batch_size = 1000
567
+ offset = 0
568
+
569
+ loop do
570
+ batch_processed = run <<~SQL
571
+ UPDATE products
572
+ SET updated_at = NOW()
573
+ WHERE id IN (
574
+ SELECT id FROM products
575
+ WHERE updated_at IS NULL
576
+ LIMIT #{batch_size} OFFSET #{offset}
577
+ )
578
+ SQL
579
+
580
+ break if batch_processed == 0
581
+ offset += batch_size
582
+ end
583
+ end
584
+
585
+ down do
586
+ # Reverse operation if needed
587
+ end
588
+ end
589
+ ```
590
+
591
+ ### 4. Testing Migrations
592
+
593
+ ```ruby
594
+ # test/migration_test.rb
595
+ require_relative 'spec_helper'
596
+
597
+ class MigrationTest < SequelDuckDBTest::TestCase
598
+ def test_migration_001_creates_users_table
599
+ # Run specific migration
600
+ Sequel::Migrator.run(@db, 'db/migrate', target: 1)
601
+
602
+ # Verify table exists
603
+ assert @db.table_exists?(:users)
604
+
605
+ # Verify schema
606
+ schema = @db.schema(:users)
607
+ assert schema.any? { |col| col[0] == :id && col[1][:primary_key] }
608
+ assert schema.any? { |col| col[0] == :name && !col[1][:allow_null] }
609
+ end
610
+
611
+ def test_migration_rollback
612
+ # Run migration
613
+ Sequel::Migrator.run(@db, 'db/migrate', target: 1)
614
+ assert @db.table_exists?(:users)
615
+
616
+ # Rollback
617
+ Sequel::Migrator.run(@db, 'db/migrate', target: 0)
618
+ refute @db.table_exists?(:users)
619
+ end
620
+ end
621
+ ```
622
+
623
+ ## Migration Runner Script
624
+
625
+ Create a script to manage migrations:
626
+
627
+ ```ruby
628
+ #!/usr/bin/env ruby
629
+ # bin/migrate
630
+
631
+ require 'sequel'
632
+ require_relative '../lib/sequel/duckdb'
633
+
634
+ # Configuration
635
+ DB_URL = ENV['DATABASE_URL'] || 'duckdb:///db/development.duckdb'
636
+ MIGRATIONS_DIR = File.expand_path('../db/migrate', __dir__)
637
+
638
+ # Connect to database
639
+ db = Sequel.connect(DB_URL)
640
+
641
+ # Parse command line arguments
642
+ command = ARGV[0]
643
+ target = ARGV[1]&.to_i
644
+
645
+ case command
646
+ when 'up', nil
647
+ puts "Running migrations..."
648
+ if target
649
+ Sequel::Migrator.run(db, MIGRATIONS_DIR, target: target)
650
+ puts "Migrated to version #{target}"
651
+ else
652
+ Sequel::Migrator.run(db, MIGRATIONS_DIR)
653
+ puts "Migrated to latest version"
654
+ end
655
+
656
+ when 'down'
657
+ target ||= 0
658
+ puts "Rolling back to version #{target}..."
659
+ Sequel::Migrator.run(db, MIGRATIONS_DIR, target: target)
660
+ puts "Rolled back to version #{target}"
661
+
662
+ when 'version'
663
+ version = db[:schema_info].first[:version] rescue 0
664
+ puts "Current migration version: #{version}"
665
+
666
+ when 'create'
667
+ name = ARGV[1]
668
+ unless name
669
+ puts "Usage: bin/migrate create migration_name"
670
+ exit 1
671
+ end
672
+
673
+ # Find next migration number
674
+ existing = Dir[File.join(MIGRATIONS_DIR, '*.rb')]
675
+ next_num = existing.map { |f| File.basename(f)[/^\d+/].to_i }.max.to_i + 1
676
+
677
+ # Create migration file
678
+ filename = format('%03d_%s.rb', next_num, name)
679
+ filepath = File.join(MIGRATIONS_DIR, filename)
680
+
681
+ File.write(filepath, <<~RUBY)
682
+ Sequel.migration do
683
+ up do
684
+ # Add your migration code here
685
+ end
686
+
687
+ down do
688
+ # Add your rollback code here
689
+ end
690
+ end
691
+ RUBY
692
+
693
+ puts "Created migration: #{filepath}"
694
+
695
+ else
696
+ puts <<~USAGE
697
+ Usage: bin/migrate [command] [options]
698
+
699
+ Commands:
700
+ up [version] - Run migrations up to specified version (or latest)
701
+ down [version] - Roll back to specified version (default: 0)
702
+ version - Show current migration version
703
+ create <name> - Create a new migration file
704
+
705
+ Examples:
706
+ bin/migrate # Run all pending migrations
707
+ bin/migrate up 5 # Migrate to version 5
708
+ bin/migrate down 3 # Roll back to version 3
709
+ bin/migrate version # Show current version
710
+ bin/migrate create add_users # Create new migration
711
+ USAGE
712
+ end
713
+ ```
714
+
715
+ Make the script executable:
716
+
717
+ ```bash
718
+ chmod +x bin/migrate
719
+ ```
720
+
721
+ ## Usage Examples
722
+
723
+ ```bash
724
+ # Run all pending migrations
725
+ ./bin/migrate
726
+
727
+ # Migrate to specific version
728
+ ./bin/migrate up 5
729
+
730
+ # Roll back to previous version
731
+ ./bin/migrate down 4
732
+
733
+ # Check current version
734
+ ./bin/migrate version
735
+
736
+ # Create new migration
737
+ ./bin/migrate create add_user_preferences
738
+ ```
739
+
740
+ This comprehensive guide covers the most common migration patterns and DuckDB-specific considerations when using Sequel migrations. Remember to always test your migrations thoroughly and keep them reversible for safe deployment practices.