db-migrate-x 0.2.0 → 0.3.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/alter-table.md +183 -0
- data/context/create-index.md +217 -0
- data/context/create-table.md +170 -0
- data/context/drop-table.md +180 -0
- data/context/getting-started.md +94 -0
- data/context/index.yaml +29 -0
- data/context/migrations.md +98 -0
- data/lib/db/migrate/alter_table.rb +154 -0
- data/lib/db/migrate/create_table.rb +1 -1
- data/lib/db/migrate/drop_index.rb +3 -1
- data/lib/db/migrate/drop_table.rb +3 -1
- data/lib/db/migrate/information_schema.rb +2 -0
- data/lib/db/migrate/migration.rb +55 -0
- data/lib/db/migrate/version.rb +1 -1
- data/readme.md +25 -1
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +17 -13
- metadata.gz.sig +0 -0
@@ -0,0 +1,180 @@
|
|
1
|
+
# Drop Table
|
2
|
+
|
3
|
+
This guide explains how to remove database tables using `db-migrate`.
|
4
|
+
|
5
|
+
## Basic Table Removal
|
6
|
+
|
7
|
+
Use `drop_table` to remove a table:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
DB::Migrate.migrate("remove_old_table", client) do
|
11
|
+
drop_table :old_users_table
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
## Safe Table Removal
|
16
|
+
|
17
|
+
Use `if_exists` to avoid errors if the table doesn't exist:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
DB::Migrate.migrate("safe_cleanup", client) do
|
21
|
+
drop_table :maybe_missing_table, if_exists: true
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
## Feature Detection
|
26
|
+
|
27
|
+
The gem automatically detects whether your database supports `IF EXISTS` clauses:
|
28
|
+
|
29
|
+
**PostgreSQL & MariaDB:**
|
30
|
+
```sql
|
31
|
+
DROP TABLE IF EXISTS old_table;
|
32
|
+
```
|
33
|
+
|
34
|
+
**Databases without IF EXISTS support:**
|
35
|
+
```sql
|
36
|
+
DROP TABLE old_table;
|
37
|
+
```
|
38
|
+
|
39
|
+
## Multiple Table Removal
|
40
|
+
|
41
|
+
Remove multiple tables in a single migration:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
DB::Migrate.migrate("cleanup_old_tables", client) do
|
45
|
+
drop_table :temp_users, if_exists: true
|
46
|
+
drop_table :old_analytics, if_exists: true
|
47
|
+
drop_table :deprecated_logs, if_exists: true
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
## Advanced Examples
|
52
|
+
|
53
|
+
### Conditional Removal
|
54
|
+
|
55
|
+
Check if a table exists before dropping it:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
DB::Migrate.migrate("conditional_cleanup", client) do
|
59
|
+
if information_schema.table_exists?(:old_table)
|
60
|
+
drop_table :old_table
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
### Table Replacement
|
66
|
+
|
67
|
+
Replace an existing table with a new structure:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
DB::Migrate.migrate("replace_users_table", client) do
|
71
|
+
# Drop the old table
|
72
|
+
drop_table :users, if_exists: true
|
73
|
+
|
74
|
+
# Create the new table
|
75
|
+
create_table :users do
|
76
|
+
primary_key
|
77
|
+
column :name, "TEXT NOT NULL"
|
78
|
+
column :email, "TEXT UNIQUE NOT NULL"
|
79
|
+
timestamps
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Dependent Table Cleanup
|
85
|
+
|
86
|
+
Remove tables in the correct order to handle dependencies:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
DB::Migrate.migrate("cleanup_related_tables", client) do
|
90
|
+
# Drop dependent tables first
|
91
|
+
drop_table :user_preferences, if_exists: true
|
92
|
+
drop_table :user_sessions, if_exists: true
|
93
|
+
|
94
|
+
# Then drop the main table
|
95
|
+
drop_table :users, if_exists: true
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
## Best Practices
|
100
|
+
|
101
|
+
### Always Use if_exists for Cleanup
|
102
|
+
|
103
|
+
When removing tables during cleanup operations, always use `if_exists`:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# Good: Safe cleanup
|
107
|
+
drop_table :temp_table, if_exists: true
|
108
|
+
|
109
|
+
# Risky: May fail if table doesn't exist
|
110
|
+
drop_table :temp_table
|
111
|
+
```
|
112
|
+
|
113
|
+
### Document Destructive Operations
|
114
|
+
|
115
|
+
Add clear comments for destructive operations:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
DB::Migrate.migrate("remove_deprecated_analytics", client) do
|
119
|
+
# WARNING: This permanently removes all analytics data from before 2023
|
120
|
+
# Ensure backup is completed before running this migration
|
121
|
+
drop_table :old_analytics_2022, if_exists: true
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### Consider Data Migration
|
126
|
+
|
127
|
+
Before dropping tables with important data, consider migrating it:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
DB::Migrate.migrate("migrate_user_data", client) do
|
131
|
+
# First, migrate important data
|
132
|
+
session.query("INSERT INTO users_new SELECT id, name, email FROM users_old")
|
133
|
+
|
134
|
+
# Then drop the old table
|
135
|
+
drop_table :users_old, if_exists: true
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
## Safety Considerations
|
140
|
+
|
141
|
+
### Backup Important Data
|
142
|
+
|
143
|
+
Always backup important data before dropping tables:
|
144
|
+
|
145
|
+
```bash
|
146
|
+
# PostgreSQL
|
147
|
+
pg_dump -t old_important_table mydb > backup.sql
|
148
|
+
|
149
|
+
# MariaDB/MySQL
|
150
|
+
mysqldump mydb old_important_table > backup.sql
|
151
|
+
```
|
152
|
+
|
153
|
+
### Test Migrations
|
154
|
+
|
155
|
+
Test destructive migrations on a copy of your production data:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# Test migration on development/staging first
|
159
|
+
DB::Migrate.migrate("test_table_removal", client) do
|
160
|
+
drop_table :test_table, if_exists: true
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
### Transaction Safety
|
165
|
+
|
166
|
+
Table drops are included in the migration transaction and will be rolled back if any subsequent operation fails:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
DB::Migrate.migrate("safe_migration", client) do
|
170
|
+
drop_table :old_table, if_exists: true
|
171
|
+
|
172
|
+
create_table :new_table do
|
173
|
+
primary_key
|
174
|
+
column :name, "TEXT NOT NULL"
|
175
|
+
end
|
176
|
+
|
177
|
+
# If this fails, the table drop above is rolled back
|
178
|
+
create_index :new_table, :name
|
179
|
+
end
|
180
|
+
```
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to get started with `db-migrate` for managing database schema changes in Ruby applications.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your project:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
$ bundle add db-migrate
|
11
|
+
```
|
12
|
+
|
13
|
+
You'll also need a database adapter. For PostgreSQL:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle add db-postgres
|
17
|
+
```
|
18
|
+
|
19
|
+
For MariaDB/MySQL:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ bundle add db-mariadb
|
23
|
+
```
|
24
|
+
|
25
|
+
## Core Concepts
|
26
|
+
|
27
|
+
`db-migrate` provides a simple and flexible way to manage database schema changes:
|
28
|
+
|
29
|
+
- {DB::Migrate::Migration} which represents a single database migration with schema changes.
|
30
|
+
- Database-agnostic migration operations that work across PostgreSQL, MariaDB, and other supported databases.
|
31
|
+
- Feature detection that automatically uses the best SQL syntax for your database.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Create and run a migration:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require "db/migrate"
|
39
|
+
require "db/postgres" # or 'db/mariadb'
|
40
|
+
|
41
|
+
# Connect to your database
|
42
|
+
client = DB::Client.new(DB::Postgres::Adapter.new(
|
43
|
+
host: "localhost",
|
44
|
+
database: "myapp_development"
|
45
|
+
))
|
46
|
+
|
47
|
+
# Define and run a migration
|
48
|
+
DB::Migrate.migrate("create_users_table", client) do
|
49
|
+
create_table :users do
|
50
|
+
primary_key
|
51
|
+
column :name, "TEXT NOT NULL"
|
52
|
+
column :email, "TEXT UNIQUE"
|
53
|
+
timestamps
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
### Running Multiple Operations
|
59
|
+
|
60
|
+
Migrations can include multiple operations:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
DB::Migrate.migrate("update_users_schema", client) do
|
64
|
+
# Add new columns
|
65
|
+
alter_table :users do
|
66
|
+
add_column :age, "INTEGER"
|
67
|
+
add_column :active, "BOOLEAN DEFAULT TRUE"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create indexes
|
71
|
+
create_index :users, :email
|
72
|
+
create_index :users, [:name, :active]
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
### Conditional Operations
|
77
|
+
|
78
|
+
Use conditional operations when you're not sure if tables or columns exist:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
DB::Migrate.migrate("safe_schema_update", client) do
|
82
|
+
# Only create table if it doesn't exist
|
83
|
+
create_table? :profiles do
|
84
|
+
primary_key
|
85
|
+
column :user_id, "BIGINT NOT NULL"
|
86
|
+
column :bio, "TEXT"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Only drop table if it exists
|
90
|
+
drop_table :old_table, if_exists: true
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
The `db-migrate` gem automatically detects your database's capabilities and only uses conditional operations (like `IF EXISTS`) when supported.
|
data/context/index.yaml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
3
|
+
---
|
4
|
+
description: Database migrations.
|
5
|
+
metadata:
|
6
|
+
documentation_uri: https://socketry.github.io/db-migrate/
|
7
|
+
funding_uri: https://github.com/sponsors/ioquatix/
|
8
|
+
source_code_uri: https://github.com/socketry/db-migrate.git
|
9
|
+
files:
|
10
|
+
- path: getting-started.md
|
11
|
+
title: Getting Started
|
12
|
+
description: This guide explains how to get started with `db-migrate` for managing
|
13
|
+
database schema changes in Ruby applications.
|
14
|
+
- path: migrations.md
|
15
|
+
title: Migrations
|
16
|
+
description: This guide explains how to create and structure database migrations
|
17
|
+
using `db-migrate`.
|
18
|
+
- path: create-table.md
|
19
|
+
title: Create Table
|
20
|
+
description: This guide explains how to create database tables using `db-migrate`.
|
21
|
+
- path: alter-table.md
|
22
|
+
title: Alter Table
|
23
|
+
description: This guide explains how to modify existing database tables using `db-migrate`.
|
24
|
+
- path: drop-table.md
|
25
|
+
title: Drop Table
|
26
|
+
description: This guide explains how to remove database tables using `db-migrate`.
|
27
|
+
- path: create-index.md
|
28
|
+
title: Create Index
|
29
|
+
description: This guide explains how to create database indexes using `db-migrate`.
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Migrations
|
2
|
+
|
3
|
+
This guide explains how to create and structure database migrations using `db-migrate`.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Migrations in `db-migrate` are Ruby blocks that define schema changes. Each migration runs inside a database transaction, ensuring consistency and allowing rollback on errors.
|
8
|
+
|
9
|
+
## Basic Migration Structure
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
DB::Migrate.migrate("migration_name", client) do
|
13
|
+
# Schema operations go here
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
## Migration Naming
|
18
|
+
|
19
|
+
Use descriptive names that indicate what the migration does:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# Good examples
|
23
|
+
DB::Migrate.migrate("create_users_table", client) do
|
24
|
+
# ...
|
25
|
+
end
|
26
|
+
|
27
|
+
DB::Migrate.migrate("add_email_index_to_users", client) do
|
28
|
+
# ...
|
29
|
+
end
|
30
|
+
|
31
|
+
DB::Migrate.migrate("remove_deprecated_columns", client) do
|
32
|
+
# ...
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
## Transaction Safety
|
37
|
+
|
38
|
+
All migration operations run inside a database transaction. If any operation fails, the entire migration is rolled back:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
DB::Migrate.migrate("complex_migration", client) do
|
42
|
+
create_table :users do
|
43
|
+
primary_key
|
44
|
+
column :name, "TEXT NOT NULL"
|
45
|
+
end
|
46
|
+
|
47
|
+
# If this fails, the table creation above is rolled back
|
48
|
+
create_index :users, :name
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
## Database Compatibility
|
53
|
+
|
54
|
+
`db-migrate` automatically detects your database's capabilities and generates appropriate SQL:
|
55
|
+
|
56
|
+
### PostgreSQL
|
57
|
+
- Uses `BIGSERIAL` for auto-increment columns
|
58
|
+
- Supports `IF EXISTS` clauses
|
59
|
+
- Uses `ALTER COLUMN ... TYPE ... USING ...` for column type changes
|
60
|
+
|
61
|
+
### MariaDB/MySQL
|
62
|
+
- Uses `BIGINT AUTO_INCREMENT` for auto-increment columns
|
63
|
+
- Supports `IF EXISTS` clauses
|
64
|
+
- Uses `MODIFY COLUMN` for column type changes
|
65
|
+
|
66
|
+
## Available Operations
|
67
|
+
|
68
|
+
### Table Operations
|
69
|
+
- `create_table(name)` - Create a new table
|
70
|
+
- `create_table?(name)` - Create table only if it doesn't exist
|
71
|
+
- `drop_table(name, if_exists: true)` - Drop a table
|
72
|
+
- `rename_table(old_name, new_name)` - Rename a table
|
73
|
+
|
74
|
+
### Column Operations
|
75
|
+
- `add_column(name, type)` - Add a new column
|
76
|
+
- `drop_column(name, if_exists: true)` - Remove a column
|
77
|
+
- `rename_column(old_name, new_name)` - Rename a column
|
78
|
+
- `change_column(name, new_type)` - Change column type
|
79
|
+
|
80
|
+
### Index Operations
|
81
|
+
- `create_index(table, columns)` - Create an index
|
82
|
+
- `drop_index(name, if_exists: true)` - Drop an index
|
83
|
+
|
84
|
+
## Information Schema Access
|
85
|
+
|
86
|
+
Query database metadata within migrations:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
DB::Migrate.migrate("conditional_migration", client) do
|
90
|
+
# Check if table exists before creating
|
91
|
+
unless information_schema.table_exists?(:users)
|
92
|
+
create_table :users do
|
93
|
+
primary_key
|
94
|
+
column :name, "TEXT NOT NULL"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2021-2024, by Samuel Williams.
|
5
|
+
|
6
|
+
module DB
|
7
|
+
module Migrate
|
8
|
+
class AlterTable
|
9
|
+
def initialize(name)
|
10
|
+
@name = name
|
11
|
+
@operations = []
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :operations
|
15
|
+
|
16
|
+
# Add a new column to the table
|
17
|
+
def add_column(name, type, **options)
|
18
|
+
@operations << [:add_column, name, type, options]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Drop a column from the table
|
22
|
+
def drop_column(name, if_exists: false)
|
23
|
+
@operations << [:drop_column, name, {if_exists: if_exists}]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Rename a column
|
27
|
+
def rename_column(old_name, new_name)
|
28
|
+
@operations << [:rename_column, old_name, new_name, {}]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Change column type or options
|
32
|
+
def change_column(name, type, **options)
|
33
|
+
@operations << [:change_column, name, type, options]
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(session)
|
37
|
+
@operations.each do |operation, *args|
|
38
|
+
case operation
|
39
|
+
when :add_column
|
40
|
+
add_column_statement(session, *args)
|
41
|
+
when :drop_column
|
42
|
+
drop_column_statement(session, *args)
|
43
|
+
when :rename_column
|
44
|
+
rename_column_statement(session, *args)
|
45
|
+
when :change_column
|
46
|
+
change_column_statement(session, *args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def add_column_statement(session, column_name, type, options)
|
54
|
+
statement = session.clause("ALTER TABLE")
|
55
|
+
statement.identifier(@name)
|
56
|
+
statement.clause("ADD COLUMN")
|
57
|
+
statement.identifier(column_name)
|
58
|
+
statement.clause(type)
|
59
|
+
|
60
|
+
if options.key?(:null) && !options[:null]
|
61
|
+
statement.clause("NOT NULL")
|
62
|
+
end
|
63
|
+
|
64
|
+
if options.key?(:default)
|
65
|
+
statement.clause("DEFAULT")
|
66
|
+
statement.literal(options[:default])
|
67
|
+
end
|
68
|
+
|
69
|
+
if options[:unique]
|
70
|
+
statement.clause("UNIQUE")
|
71
|
+
end
|
72
|
+
|
73
|
+
Console.logger.info(self, statement)
|
74
|
+
statement.call
|
75
|
+
end
|
76
|
+
|
77
|
+
def drop_column_statement(session, column_name, options)
|
78
|
+
statement = session.clause("ALTER TABLE")
|
79
|
+
statement.identifier(@name)
|
80
|
+
statement.clause("DROP COLUMN")
|
81
|
+
|
82
|
+
# Use feature detection for IF EXISTS support
|
83
|
+
features = session.connection.features
|
84
|
+
if options[:if_exists] && features.conditional_operations?
|
85
|
+
statement.clause("IF EXISTS")
|
86
|
+
end
|
87
|
+
|
88
|
+
statement.identifier(column_name)
|
89
|
+
|
90
|
+
Console.logger.info(self, statement)
|
91
|
+
statement.call
|
92
|
+
end
|
93
|
+
|
94
|
+
def rename_column_statement(session, old_name, new_name, options)
|
95
|
+
statement = session.clause("ALTER TABLE")
|
96
|
+
statement.identifier(@name)
|
97
|
+
statement.clause("RENAME COLUMN")
|
98
|
+
statement.identifier(old_name)
|
99
|
+
statement.clause("TO")
|
100
|
+
statement.identifier(new_name)
|
101
|
+
|
102
|
+
Console.logger.info(self, statement)
|
103
|
+
statement.call
|
104
|
+
end
|
105
|
+
|
106
|
+
def change_column_statement(session, column_name, type, options)
|
107
|
+
# Use feature detection for database-specific syntax
|
108
|
+
features = session.connection.features
|
109
|
+
|
110
|
+
if features.modify_column?
|
111
|
+
# MySQL/MariaDB syntax: MODIFY COLUMN
|
112
|
+
statement = session.clause("ALTER TABLE")
|
113
|
+
statement.identifier(@name)
|
114
|
+
statement.clause("MODIFY COLUMN")
|
115
|
+
statement.identifier(column_name)
|
116
|
+
statement.clause(type)
|
117
|
+
|
118
|
+
Console.logger.info(self, statement)
|
119
|
+
statement.call
|
120
|
+
elsif features.alter_column_type?
|
121
|
+
# PostgreSQL syntax: ALTER COLUMN ... TYPE ... USING ...
|
122
|
+
statement = session.clause("ALTER TABLE")
|
123
|
+
statement.identifier(@name)
|
124
|
+
statement.clause("ALTER COLUMN")
|
125
|
+
statement.identifier(column_name)
|
126
|
+
statement.clause("TYPE")
|
127
|
+
statement.clause(type)
|
128
|
+
|
129
|
+
if features.using_clause?
|
130
|
+
# Add USING clause for safe conversion
|
131
|
+
statement.clause("USING")
|
132
|
+
statement.identifier(column_name)
|
133
|
+
statement.clause("::")
|
134
|
+
statement.clause(type)
|
135
|
+
end
|
136
|
+
|
137
|
+
Console.logger.info(self, statement)
|
138
|
+
statement.call
|
139
|
+
else
|
140
|
+
# Generic syntax for unsupported databases (default to PostgreSQL-style)
|
141
|
+
statement = session.clause("ALTER TABLE")
|
142
|
+
statement.identifier(@name)
|
143
|
+
statement.clause("ALTER COLUMN")
|
144
|
+
statement.identifier(column_name)
|
145
|
+
statement.clause("TYPE")
|
146
|
+
statement.clause(type)
|
147
|
+
|
148
|
+
Console.logger.info(self, statement)
|
149
|
+
statement.call
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -18,7 +18,9 @@ module DB
|
|
18
18
|
def call(session)
|
19
19
|
statement = session.clause("DROP INDEX")
|
20
20
|
|
21
|
-
|
21
|
+
# Use feature detection for IF EXISTS support
|
22
|
+
features = session.connection.features
|
23
|
+
if @if_exists && features.conditional_operations?
|
22
24
|
statement.clause("IF EXISTS")
|
23
25
|
end
|
24
26
|
|
@@ -20,7 +20,9 @@ module DB
|
|
20
20
|
def call(session)
|
21
21
|
statement = session.clause("DROP TABLE")
|
22
22
|
|
23
|
-
|
23
|
+
# Use feature detection for IF EXISTS support
|
24
|
+
features = session.connection.features
|
25
|
+
if @if_exists && features.conditional_operations?
|
24
26
|
statement.clause("IF EXISTS")
|
25
27
|
end
|
26
28
|
|
data/lib/db/migrate/migration.rb
CHANGED
@@ -4,10 +4,12 @@
|
|
4
4
|
# Copyright, 2021-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require "async"
|
7
|
+
require "console"
|
7
8
|
|
8
9
|
require_relative "create_table"
|
9
10
|
require_relative "rename_table"
|
10
11
|
require_relative "create_index"
|
12
|
+
require_relative "alter_table"
|
11
13
|
|
12
14
|
module DB
|
13
15
|
module Migrate
|
@@ -17,6 +19,8 @@ module DB
|
|
17
19
|
@session = session
|
18
20
|
end
|
19
21
|
|
22
|
+
attr_reader :name, :session
|
23
|
+
|
20
24
|
def call(&block)
|
21
25
|
create_table?(:migration) do
|
22
26
|
primary_key
|
@@ -24,7 +28,19 @@ module DB
|
|
24
28
|
timestamps
|
25
29
|
end
|
26
30
|
|
31
|
+
# Check if migration has already been executed
|
32
|
+
if migration_exists?
|
33
|
+
Console.logger.info(self, "Migration '#{@name}' already executed, skipping...")
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
# Execute the migration
|
38
|
+
Console.logger.info(self, "Running migration '#{@name}'...")
|
27
39
|
self.instance_eval(&block)
|
40
|
+
|
41
|
+
# Record successful migration
|
42
|
+
record_migration
|
43
|
+
Console.logger.info(self, "Migration '#{@name}' completed successfully.")
|
28
44
|
end
|
29
45
|
|
30
46
|
def information_schema
|
@@ -59,6 +75,45 @@ module DB
|
|
59
75
|
drop_table = DropTable.new(name, if_exists: if_exists)
|
60
76
|
drop_table.call(@session)
|
61
77
|
end
|
78
|
+
|
79
|
+
def alter_table(name, &block)
|
80
|
+
alter_table = AlterTable.new(name)
|
81
|
+
alter_table.instance_eval(&block)
|
82
|
+
alter_table.call(@session)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Check if this migration has already been executed
|
88
|
+
def migration_exists?
|
89
|
+
statement = @session.clause("SELECT COUNT(*) FROM")
|
90
|
+
statement.identifier(:migration)
|
91
|
+
statement.clause("WHERE")
|
92
|
+
statement.identifier(:name)
|
93
|
+
statement.clause("=")
|
94
|
+
statement.literal(@name)
|
95
|
+
|
96
|
+
result = statement.call
|
97
|
+
count = result.to_a.first.first
|
98
|
+
count > 0
|
99
|
+
end
|
100
|
+
|
101
|
+
# Record that this migration has been executed
|
102
|
+
def record_migration
|
103
|
+
statement = @session.clause("INSERT INTO")
|
104
|
+
statement.identifier(:migration)
|
105
|
+
statement.clause("(")
|
106
|
+
statement.identifier(:name)
|
107
|
+
statement.clause(",")
|
108
|
+
statement.identifier(:created_at)
|
109
|
+
statement.clause(",")
|
110
|
+
statement.identifier(:updated_at)
|
111
|
+
statement.clause(") VALUES (")
|
112
|
+
statement.literal(@name)
|
113
|
+
statement.clause(", NOW(), NOW())")
|
114
|
+
|
115
|
+
statement.call
|
116
|
+
end
|
62
117
|
end
|
63
118
|
|
64
119
|
def self.migrate(name, client, &block)
|