ridgepole-ext-tidb 0.2.1 → 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
- data/README.md +37 -46
- data/docker-compose.yml +5 -30
- data/lib/ridgepole/ext_tidb/detector.rb +69 -0
- data/lib/ridgepole/ext_tidb/dump_patch.rb +80 -0
- data/lib/ridgepole/ext_tidb/install.rb +146 -0
- data/lib/ridgepole/ext_tidb/schema_creation.rb +91 -0
- data/lib/ridgepole/ext_tidb/table_options.rb +79 -0
- data/lib/ridgepole/ext_tidb/version.rb +7 -0
- data/lib/ridgepole/ext_tidb.rb +20 -0
- data/ridgepole-ext-tidb.gemspec +6 -6
- metadata +20 -16
- data/lib/ridgepole/ext/tidb/version.rb +0 -9
- data/lib/ridgepole/ext/tidb.rb +0 -266
- data/lib/ridgepole-ext-tidb.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35422c4801686a578e9871993e7ea61443110ef8ce39b2ba31510984824c2559
|
4
|
+
data.tar.gz: 4347c14f4e1990409c665ee39742111f68d214e3ad11fab549222564c5adcfec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50330dcb777c17cc030d4569d03988b8343f50a4dbf1537bd21f8203a085cca3b681ff02d7b3ca0ff8158bb1ffb56ba41a1fd1b6fe02c939073b085de1c2bbcb
|
7
|
+
data.tar.gz: 8cf011251797a1d67bad7476dc1c374f00b630306314400dcdac5198d81a82374d41ad00b811e048106f1616eb10ac2daea163380f7ea51651bd95c0a70b6583
|
data/README.md
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
# Ridgepole::Ext::Tidb
|
2
2
|
|
3
3
|

|
4
|
-

|
5
5
|

|
6
6
|
|
7
7
|
TiDBの`AUTO_RANDOM`カラム属性をサポートするRidgepole拡張機能です。この拡張により、TiDBの分散IDジェネレーション機能をSchemafile管理に統合できます。
|
8
8
|
|
9
9
|
## 主な機能
|
10
10
|
|
11
|
-
- **AUTO_RANDOM
|
12
|
-
- **
|
13
|
-
-
|
14
|
-
-
|
15
|
-
-
|
16
|
-
- **
|
11
|
+
- **AUTO_RANDOM適用**: CREATE時に`AUTO_RANDOM(n)`を列定義へ付与し、`AUTO_INCREMENT`を抑止
|
12
|
+
- **AUTO_RANDOM_BASE**: テーブルオプションに`AUTO_RANDOM_BASE=<n>`を付与
|
13
|
+
- **スキーマダンプ対応**: `create_table`のオプションへ`auto_random:`/`auto_random_base:`を出力(往復一致)
|
14
|
+
- **冪等性**: apply→export→diff→applyの繰り返しでも差分ゼロを維持(ALTERは不使用)
|
15
|
+
- **TiDB判定**: 接続先がTiDBかどうかを自動判定
|
16
|
+
- **MySQL互換**: mysql2 / trilogy アダプターの両方に対応
|
17
|
+
- **Ruby 3.2+ 対応**
|
17
18
|
|
18
19
|
## インストール
|
19
20
|
|
@@ -41,10 +42,7 @@ $ gem install ridgepole-ext-tidb
|
|
41
42
|
|
42
43
|
```ruby
|
43
44
|
require 'ridgepole'
|
44
|
-
require 'ridgepole
|
45
|
-
|
46
|
-
# TiDB拡張をセットアップ(ActiveRecord読み込み後)
|
47
|
-
Ridgepole::Ext::Tidb.setup!
|
45
|
+
require 'ridgepole/ext_tidb'
|
48
46
|
|
49
47
|
# Ridgepoleクライアントを設定
|
50
48
|
client = Ridgepole::Client.new({
|
@@ -61,22 +59,25 @@ client = Ridgepole::Client.new({
|
|
61
59
|
|
62
60
|
```ruby
|
63
61
|
# Schemafile
|
64
|
-
|
62
|
+
require "ridgepole/ext_tidb"
|
63
|
+
|
64
|
+
# 1) テーブルレベルでAUTO_RANDOMとAUTO_RANDOM_BASEを指定
|
65
|
+
create_table "users",
|
66
|
+
id: :bigint,
|
67
|
+
auto_random: 5,
|
68
|
+
auto_random_base: 100_000,
|
69
|
+
options: "DEFAULT CHARSET=utf8mb4" do |t|
|
65
70
|
t.string :name, null: false
|
66
|
-
t.string :email, null: false
|
67
|
-
t.timestamps
|
68
71
|
end
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
t.bigint :
|
73
|
+
# 2) 手動PK(カラム側でAUTO_RANDOMを指定)
|
74
|
+
create_table "events", id: false, options: "DEFAULT CHARSET=utf8mb4" do |t|
|
75
|
+
t.bigint :id, primary_key: true, null: false, auto_random: 6
|
73
76
|
t.string :title, null: false
|
74
|
-
t.text :content
|
75
|
-
t.timestamps
|
76
77
|
end
|
77
78
|
```
|
78
79
|
|
79
|
-
|
80
|
+
出力(export)は`create_table`のオプションに`auto_random:`/`auto_random_base:`を含めて往復一致となります(ALTERは使用しません)。
|
80
81
|
|
81
82
|
### CLI使用例
|
82
83
|
|
@@ -102,11 +103,10 @@ $ bundle exec ridgepole -c config/database.yml -E development --export -o Schema
|
|
102
103
|
|
103
104
|
## 動作確認済み環境
|
104
105
|
|
105
|
-
-
|
106
|
-
-
|
107
|
-
-
|
108
|
-
-
|
109
|
-
- **アダプター**: mysql2, trilogy
|
106
|
+
- TiDB: v7.5.0 以降
|
107
|
+
- ActiveRecord: 7 / 8 系
|
108
|
+
- Ridgepole: 3.0.4 以降
|
109
|
+
- アダプター: mysql2 / trilogy
|
110
110
|
|
111
111
|
## データベース設定
|
112
112
|
|
@@ -154,7 +154,8 @@ connection.auto_random_column?('users', 'id') # => true/false
|
|
154
154
|
|
155
155
|
### 3. スキーマダンプ対応
|
156
156
|
|
157
|
-
|
157
|
+
既存テーブルからのダンプ時、SHOW CREATE を解析して `create_table` のオプションに
|
158
|
+
`auto_random:` と `auto_random_base:` を出力します。apply→export→diff→apply の繰り返しでも差分は発生しません。
|
158
159
|
|
159
160
|
## テスト結果例
|
160
161
|
|
@@ -213,7 +214,7 @@ CREATE TABLE users (
|
|
213
214
|
|
214
215
|
### 前提条件
|
215
216
|
|
216
|
-
- Ruby 3.
|
217
|
+
- Ruby 3.2 以上
|
217
218
|
- TiDB 4.0 以上 (テスト用)
|
218
219
|
- Docker (テスト環境用)
|
219
220
|
|
@@ -227,31 +228,21 @@ $ bundle install
|
|
227
228
|
|
228
229
|
### テスト実行
|
229
230
|
|
230
|
-
|
231
|
-
# 基本機能テスト(TiDBなしでも実行可能)
|
232
|
-
$ SKIP_TIDB_TESTS=1 bundle exec rspec
|
233
|
-
|
234
|
-
# TiDB統合テスト(Dockerが必要)
|
235
|
-
$ docker compose up -d tidb
|
236
|
-
$ bundle exec rspec
|
237
|
-
|
238
|
-
# Docker環境でのテスト
|
239
|
-
$ docker compose run --rm test
|
240
|
-
```
|
241
|
-
|
242
|
-
### TiDBテスト環境
|
231
|
+
このリポジトリのRSpecは、実行時に自動で TiDB コンテナ(docker compose)を起動・停止します。事前の手動起動は不要です。
|
243
232
|
|
244
|
-
|
233
|
+
前提: Docker と docker compose が使用可能で、ポート `14000` が空いていること。
|
245
234
|
|
246
235
|
```bash
|
247
|
-
# TiDB
|
248
|
-
$
|
236
|
+
# 統合テスト(TiDBを自動起動)
|
237
|
+
$ bundle exec rspec --format documentation
|
249
238
|
|
250
|
-
#
|
251
|
-
|
252
|
-
|
239
|
+
# アダプタを切り替えたい場合(デフォルトは trilogy)
|
240
|
+
# ※ mysql2 を使う場合は、別途 mysql2 をインストールしてください
|
241
|
+
$ AR_ADAPTER=mysql2 bundle exec rspec --format documentation
|
253
242
|
```
|
254
243
|
|
244
|
+
ヒント: コンテナを手動で起動しておきたい場合は `docker compose up -d tidb` を先に実行しても構いません(テストはそのまま動作します)。
|
245
|
+
|
255
246
|
## Contributing
|
256
247
|
|
257
248
|
1. このリポジトリをフォーク
|
data/docker-compose.yml
CHANGED
@@ -1,34 +1,9 @@
|
|
1
1
|
services:
|
2
2
|
tidb:
|
3
|
-
image: pingcap/tidb:
|
4
|
-
|
3
|
+
image: pingcap/tidb:latest
|
4
|
+
command: ["--store=mocktikv", "--log-file="]
|
5
5
|
ports:
|
6
|
-
- "14000:4000"
|
7
|
-
- "14080:10080"
|
8
|
-
command:
|
9
|
-
- --store=unistore
|
10
|
-
- --host=0.0.0.0
|
11
|
-
- --path=""
|
12
|
-
healthcheck:
|
13
|
-
test: ["CMD-SHELL", "timeout 1 bash -c '</dev/tcp/127.0.0.1/4000' || exit 1"]
|
14
|
-
interval: 10s
|
15
|
-
timeout: 5s
|
16
|
-
retries: 5
|
17
|
-
start_period: 30s
|
18
|
-
restart: unless-stopped
|
19
|
-
|
20
|
-
test:
|
21
|
-
build: .
|
22
|
-
depends_on:
|
23
|
-
tidb:
|
24
|
-
condition: service_healthy
|
6
|
+
- "14000:4000" # MySQL protocol
|
25
7
|
environment:
|
26
|
-
|
27
|
-
-
|
28
|
-
- TIDB_USER=root
|
29
|
-
- TIDB_PASSWORD=
|
30
|
-
- TIDB_DATABASE=ridgepole_test
|
31
|
-
working_dir: /app
|
32
|
-
volumes:
|
33
|
-
- .:/app
|
34
|
-
command: bundle exec rspec
|
8
|
+
# TiDB は root 無パスで入れる
|
9
|
+
- TZ=UTC
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module ExtTidb
|
5
|
+
module Detector
|
6
|
+
# TiDB接続の自動検出
|
7
|
+
def tidb?
|
8
|
+
return @tidb_detected if defined?(@tidb_detected)
|
9
|
+
|
10
|
+
@tidb_detected = begin
|
11
|
+
version_info = select_value('SELECT VERSION()')
|
12
|
+
result = version_info&.include?('TiDB') == true
|
13
|
+
Rails.logger.debug "TiDB detection: version=#{version_info}, result=#{result}" if defined?(Rails)
|
14
|
+
result
|
15
|
+
rescue => e
|
16
|
+
Rails.logger.debug "TiDB detection failed: #{e.message}" if defined?(Rails)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# AUTO_RANDOMカラムの検出
|
22
|
+
def auto_random_column?(table_name, column_name)
|
23
|
+
return false unless tidb?
|
24
|
+
|
25
|
+
# SHOW CREATE TABLEでAUTO_RANDOMを検出
|
26
|
+
result = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}")
|
27
|
+
create_sql = result.first[1] if result.first
|
28
|
+
|
29
|
+
if create_sql
|
30
|
+
# TiDBでのAUTO_RANDOM検出パターン
|
31
|
+
patterns = [
|
32
|
+
/AUTO_RANDOM\(\d+\)/i,
|
33
|
+
/\/\*T!\[auto_rand\] AUTO_RANDOM\(\d+\) \*\//i
|
34
|
+
]
|
35
|
+
|
36
|
+
patterns.any? { |pattern| create_sql.match?(pattern) }
|
37
|
+
else
|
38
|
+
# フォールバック: INFORMATION_SCHEMA.COLUMNSを確認
|
39
|
+
extra = select_value(<<~SQL)
|
40
|
+
SELECT EXTRA
|
41
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
42
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
43
|
+
AND TABLE_NAME = #{quote(table_name)}
|
44
|
+
AND COLUMN_NAME = #{quote(column_name)}
|
45
|
+
SQL
|
46
|
+
|
47
|
+
extra&.downcase&.include?('auto_random') == true
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
Rails.logger.debug "AUTO_RANDOM detection failed: #{e.message}" if defined?(Rails)
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# AUTO_RANDOM_BASEテーブルオプションの検出
|
55
|
+
def auto_random_base(table_name)
|
56
|
+
return nil unless tidb?
|
57
|
+
|
58
|
+
result = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}")
|
59
|
+
create_sql = result.first[1] if result.first
|
60
|
+
|
61
|
+
if create_sql&.match(/AUTO_RANDOM_BASE=(\d+)/i)
|
62
|
+
$1.to_i
|
63
|
+
end
|
64
|
+
rescue
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module ExtTidb
|
5
|
+
module DumpPatch
|
6
|
+
def dump(*args)
|
7
|
+
dsl = super
|
8
|
+
|
9
|
+
conn = ActiveRecord::Base.connection
|
10
|
+
return dsl unless conn.respond_to?(:tidb?) && conn.tidb?
|
11
|
+
|
12
|
+
begin
|
13
|
+
tables = conn.tables
|
14
|
+
rescue StandardError
|
15
|
+
return dsl
|
16
|
+
end
|
17
|
+
|
18
|
+
tables.each do |table|
|
19
|
+
extras = extract_table_auto_random_options_for_dump(conn, table)
|
20
|
+
next if extras.empty?
|
21
|
+
|
22
|
+
# inject into create_table line
|
23
|
+
dsl = inject_table_options_line(dsl, table, extras)
|
24
|
+
end
|
25
|
+
|
26
|
+
dsl
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def extract_table_auto_random_options_for_dump(conn, table)
|
32
|
+
extras = {}
|
33
|
+
begin
|
34
|
+
row = conn.execute("SHOW CREATE TABLE #{conn.quote_table_name(table)}").first
|
35
|
+
create_sql = row[1] if row
|
36
|
+
return extras unless create_sql
|
37
|
+
|
38
|
+
if (m = create_sql.match(/AUTO_RANDOM\((\d+)\)/i))
|
39
|
+
extras[:auto_random] = m[1].to_i
|
40
|
+
end
|
41
|
+
if (m = create_sql.match(/AUTO_RANDOM_BASE=(\d+)/i))
|
42
|
+
extras[:auto_random_base] = m[1].to_i
|
43
|
+
end
|
44
|
+
rescue StandardError
|
45
|
+
end
|
46
|
+
extras
|
47
|
+
end
|
48
|
+
|
49
|
+
def inject_table_options_line(dsl, table, extras)
|
50
|
+
keyvals = []
|
51
|
+
keyvals << "auto_random: #{extras[:auto_random]}" if extras[:auto_random]
|
52
|
+
keyvals << "auto_random_base: #{extras[:auto_random_base]}" if extras[:auto_random_base]
|
53
|
+
return dsl if keyvals.empty?
|
54
|
+
|
55
|
+
pattern = /(^(\s*)create_table\s+"#{Regexp.escape(table)}",\s*)(.+?)(\s+do\s*\|t\|)/m
|
56
|
+
dsl.sub(pattern) do
|
57
|
+
head = Regexp.last_match(1)
|
58
|
+
indent = Regexp.last_match(2)
|
59
|
+
opts = Regexp.last_match(3)
|
60
|
+
tail = Regexp.last_match(4)
|
61
|
+
|
62
|
+
# 既に同キーがあるなら上書きはせずそのまま
|
63
|
+
already = keyvals.any? { |kv| opts.include?(kv.split(':').first + ':') }
|
64
|
+
if already
|
65
|
+
head + opts + tail
|
66
|
+
else
|
67
|
+
injected = keyvals.join(', ')
|
68
|
+
# id: が先頭にあるならその直後に差し込む。なければ先頭に追加。
|
69
|
+
if opts =~ /(id:\s*[^,]+,\s*)/i
|
70
|
+
head + opts.sub($1, "#{$1}#{injected}, ") + tail
|
71
|
+
else
|
72
|
+
head + "#{injected}, " + opts + tail
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module ExtTidb
|
5
|
+
module Install
|
6
|
+
# Ridgepole起動時にTiDB検出→各パッチをprepend
|
7
|
+
def self.apply_patches!
|
8
|
+
# Hash#assert_valid_keysを拡張してauto_randomキーを許可
|
9
|
+
extend_hash_assert_valid_keys
|
10
|
+
|
11
|
+
# ActiveRecordが読み込まれている場合は即座に適用
|
12
|
+
if defined?(ActiveRecord::Base)
|
13
|
+
apply_activerecord_patches
|
14
|
+
else
|
15
|
+
# ActiveRecordが後でロードされる場合に備えてフックを設定
|
16
|
+
ActiveSupport.on_load(:active_record) do
|
17
|
+
apply_activerecord_patches
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.apply_activerecord_patches
|
23
|
+
extend_connection_adapters
|
24
|
+
# SchemaDumper への直接パッチは不要(dump は DumpPatch が担当)
|
25
|
+
# TableDefinition 拡張も不要(Hash#assert_valid_keys 拡張で回避)
|
26
|
+
install_connection_hook
|
27
|
+
extend_ridgepole_client
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.extend_hash_assert_valid_keys
|
33
|
+
Hash.class_eval do
|
34
|
+
alias_method :assert_valid_keys_without_auto_random, :assert_valid_keys
|
35
|
+
def assert_valid_keys(*valid_keys)
|
36
|
+
# auto_random, auto_random_baseキーを有効なキーとして追加
|
37
|
+
auto_random_keys = [:auto_random, :auto_random_base]
|
38
|
+
auto_random_keys.each do |key|
|
39
|
+
if keys.include?(key) && !valid_keys.include?(key)
|
40
|
+
valid_keys = valid_keys + [key]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
assert_valid_keys_without_auto_random(*valid_keys)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue NameError => e
|
47
|
+
Rails.logger.debug "Could not extend Hash#assert_valid_keys: #{e.message}" if defined?(Rails)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.extend_connection_adapters
|
51
|
+
return unless defined?(ActiveRecord::ConnectionAdapters)
|
52
|
+
|
53
|
+
# まずは抽象 MySQL アダプタにパッチ(mysql2/trilogy 双方を網羅)
|
54
|
+
begin
|
55
|
+
abstract_mysql = ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
|
56
|
+
|
57
|
+
abstract_mysql.prepend(Detector) unless abstract_mysql < Detector
|
58
|
+
abstract_mysql.prepend(TableOptions) unless abstract_mysql < TableOptions
|
59
|
+
|
60
|
+
if abstract_mysql.const_defined?(:SchemaCreation)
|
61
|
+
sc = abstract_mysql.const_get(:SchemaCreation)
|
62
|
+
sc.prepend(SchemaCreation) unless sc < SchemaCreation
|
63
|
+
end
|
64
|
+
rescue NameError
|
65
|
+
# 未ロードの環境もあるので無視(接続確立後にロードされる)
|
66
|
+
end
|
67
|
+
|
68
|
+
# 既に具体アダプタがロード済みなら、そちらにも適用(冪等)
|
69
|
+
%w[
|
70
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
71
|
+
ActiveRecord::ConnectionAdapters::TrilogyAdapter
|
72
|
+
].each do |adapter_name|
|
73
|
+
begin
|
74
|
+
adapter_class = Object.const_get(adapter_name)
|
75
|
+
adapter_class.prepend(Detector) unless adapter_class < Detector
|
76
|
+
adapter_class.prepend(TableOptions) unless adapter_class < TableOptions
|
77
|
+
if adapter_class.const_defined?(:SchemaCreation)
|
78
|
+
sc = adapter_class.const_get(:SchemaCreation)
|
79
|
+
sc.prepend(SchemaCreation) unless sc < SchemaCreation
|
80
|
+
end
|
81
|
+
rescue NameError
|
82
|
+
# 未ロードならスキップ
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# 名前空間の揺れに備え、ConnectionAdapters 配下の SchemaCreation すべてに適用
|
87
|
+
begin
|
88
|
+
ObjectSpace.each_object(Class) do |klass|
|
89
|
+
name = klass.name rescue nil
|
90
|
+
next unless name && name.start_with?("ActiveRecord::ConnectionAdapters")
|
91
|
+
next unless name.end_with?("::SchemaCreation")
|
92
|
+
klass.prepend(SchemaCreation) unless klass < SchemaCreation
|
93
|
+
end
|
94
|
+
rescue StandardError
|
95
|
+
# noop
|
96
|
+
end
|
97
|
+
|
98
|
+
# AbstractMysqlAdapter のサブクラスにも Detector/TableOptions を適用(ロード順対策)
|
99
|
+
begin
|
100
|
+
if defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter)
|
101
|
+
ObjectSpace.each_object(Class) do |klass|
|
102
|
+
next unless klass < ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
|
103
|
+
klass.prepend(Detector) unless klass < Detector
|
104
|
+
klass.prepend(TableOptions) unless klass < TableOptions
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue StandardError
|
108
|
+
# noop
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# 接続確立後(アダプタ読み込み後)にも確実にパッチを適用するためのフック
|
113
|
+
def self.install_connection_hook
|
114
|
+
return unless defined?(ActiveRecord::Base)
|
115
|
+
return if @establish_hook_installed
|
116
|
+
|
117
|
+
mod = Module.new do
|
118
|
+
def establish_connection(*args)
|
119
|
+
result = super
|
120
|
+
# アダプタが読み込まれた後に再度パッチ適用(冪等)
|
121
|
+
Ridgepole::ExtTidb::Install.extend_connection_adapters
|
122
|
+
result
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# class << だとローカル変数がスコープ外になるため、singleton_class で prepend
|
127
|
+
ActiveRecord::Base.singleton_class.prepend(mod)
|
128
|
+
|
129
|
+
@establish_hook_installed = true
|
130
|
+
rescue StandardError => e
|
131
|
+
Rails.logger.debug "Could not install establish_connection hook: #{e.message}" if defined?(Rails)
|
132
|
+
end
|
133
|
+
|
134
|
+
# extend_schema_dumper: dump は DumpPatch に委譲するため不要
|
135
|
+
# extend_table_definition: Hash#assert_valid_keys で未知キーを許すため不要
|
136
|
+
|
137
|
+
def self.extend_ridgepole_client
|
138
|
+
return unless defined?(Ridgepole::Client)
|
139
|
+
return if Ridgepole::Client < Ridgepole::ExtTidb::DumpPatch
|
140
|
+
Ridgepole::Client.prepend(Ridgepole::ExtTidb::DumpPatch)
|
141
|
+
rescue StandardError => e
|
142
|
+
Rails.logger.debug "Could not extend Ridgepole::Client: #{e.message}" if defined?(Rails)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module ExtTidb
|
5
|
+
module SchemaCreation
|
6
|
+
# この SchemaCreation インスタンスから接続の tidb? を参照できるように
|
7
|
+
def tidb?
|
8
|
+
if instance_variable_defined?(:@conn)
|
9
|
+
conn = instance_variable_get(:@conn)
|
10
|
+
return conn.respond_to?(:tidb?) && conn.tidb?
|
11
|
+
end
|
12
|
+
false
|
13
|
+
end
|
14
|
+
# デフォルト主キーの生成経路でも AUTO_RANDOM を反映
|
15
|
+
def visit_PrimaryKeyDefinition(o)
|
16
|
+
return super unless tidb?
|
17
|
+
|
18
|
+
auto_random_value = o.options.delete(:auto_random)
|
19
|
+
sql = super
|
20
|
+
|
21
|
+
if auto_random_value
|
22
|
+
sql.sub!(/\sAUTO_INCREMENT\b/i, "")
|
23
|
+
bits = (auto_random_value == true ? nil : Integer(auto_random_value) rescue nil)
|
24
|
+
sql << (bits ? " AUTO_RANDOM(#{bits})" : " AUTO_RANDOM")
|
25
|
+
end
|
26
|
+
|
27
|
+
sql
|
28
|
+
end
|
29
|
+
|
30
|
+
# APPLY: カラムに AUTO_RANDOM(n) を追加(PRIMARY KEY の有無に関わらず列側に付与)
|
31
|
+
def visit_ColumnDefinition(o)
|
32
|
+
return super unless tidb?
|
33
|
+
|
34
|
+
# AUTO_RANDOM を取り出し(未知キーassertは Hash 拡張で回避済み)
|
35
|
+
auto_random_value = o.options.delete(:auto_random)
|
36
|
+
|
37
|
+
sql = super
|
38
|
+
|
39
|
+
# テーブルレベル指定のフォールバック(id の自動生成経路で失われた場合に備える)
|
40
|
+
if !auto_random_value && o.respond_to?(:primary_key?) && o.primary_key?
|
41
|
+
if instance_variable_defined?(:@conn)
|
42
|
+
conn = instance_variable_get(:@conn)
|
43
|
+
if conn.instance_variable_defined?(:@tidb_pending_auto_random_pk_bits)
|
44
|
+
auto_random_value = conn.instance_variable_get(:@tidb_pending_auto_random_pk_bits)
|
45
|
+
begin
|
46
|
+
conn.remove_instance_variable(:@tidb_pending_auto_random_pk_bits)
|
47
|
+
rescue StandardError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if auto_random_value
|
54
|
+
# 念のため AUTO_INCREMENT を除去
|
55
|
+
sql.sub!(/\sAUTO_INCREMENT\b/i, "")
|
56
|
+
|
57
|
+
bits = (auto_random_value == true ? nil : Integer(auto_random_value) rescue nil)
|
58
|
+
sql << (bits ? " AUTO_RANDOM(#{bits})" : " AUTO_RANDOM")
|
59
|
+
end
|
60
|
+
|
61
|
+
sql
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def add_column_options!(sql, options)
|
67
|
+
# 独自キーを先に取り出し
|
68
|
+
bits = options.delete(:auto_random)
|
69
|
+
|
70
|
+
# AUTO_RANDOM を付けるなら AUTO_INCREMENT を抑止
|
71
|
+
options[:auto_increment] = false if bits
|
72
|
+
|
73
|
+
super(sql, options)
|
74
|
+
|
75
|
+
if bits
|
76
|
+
unless sql.match?(/\bAUTO_RANDOM\b/i)
|
77
|
+
sql.sub!(/\sAUTO_INCREMENT\b/i, "")
|
78
|
+
sql << " AUTO_RANDOM(#{Integer(bits)})"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
sql
|
83
|
+
end
|
84
|
+
|
85
|
+
# 一部の AR バージョンはこちらを呼ぶ場合があるため両方用意
|
86
|
+
def add_column_options(sql, options)
|
87
|
+
add_column_options!(sql, options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module ExtTidb
|
5
|
+
# TiDB 向け create_table 拡張(CREATE 時に完結)
|
6
|
+
# - テーブルレベル auto_random を id 定義へ伝播
|
7
|
+
# - AUTO_RANDOM_BASE をテーブル options に付与
|
8
|
+
module TableOptions
|
9
|
+
# CREATE 時に auto_random / auto_random_base を正しく反映
|
10
|
+
def create_table(table_name, **options, &block)
|
11
|
+
return super unless tidb?
|
12
|
+
|
13
|
+
# 独自キーを取り出す(AR の未知キー検証を回避)
|
14
|
+
auto_random_value = options.delete(:auto_random)
|
15
|
+
auto_random_base_value = options.delete(:auto_random_base)
|
16
|
+
|
17
|
+
# id: false でなければ、テーブルレベル auto_random を id に伝播
|
18
|
+
if auto_random_value && options[:id] != false
|
19
|
+
id_opt = options[:id]
|
20
|
+
id_opts = case id_opt
|
21
|
+
when Hash
|
22
|
+
id_opt.dup
|
23
|
+
when Symbol
|
24
|
+
{ type: id_opt }
|
25
|
+
when true, nil
|
26
|
+
{}
|
27
|
+
else
|
28
|
+
{ type: id_opt }
|
29
|
+
end
|
30
|
+
id_opts[:auto_random] = auto_random_value
|
31
|
+
id_opts[:auto_increment] = false
|
32
|
+
options[:id] = id_opts
|
33
|
+
end
|
34
|
+
|
35
|
+
# AUTO_RANDOM_BASE を options に付与
|
36
|
+
if auto_random_base_value
|
37
|
+
existing_options = options[:options] || ""
|
38
|
+
if existing_options.present?
|
39
|
+
options[:options] = "#{existing_options} AUTO_RANDOM_BASE=#{auto_random_base_value}"
|
40
|
+
else
|
41
|
+
options[:options] = "AUTO_RANDOM_BASE=#{auto_random_base_value}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# 主キー経路でビット数が落ちる環境向けのフォールバック用に一時保存
|
46
|
+
if auto_random_value && options[:id] != false
|
47
|
+
begin
|
48
|
+
@tidb_pending_auto_random_pk_bits = auto_random_value
|
49
|
+
rescue StandardError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
super
|
55
|
+
ensure
|
56
|
+
remove_instance_variable(:@tidb_pending_auto_random_pk_bits) if instance_variable_defined?(:@tidb_pending_auto_random_pk_bits)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def build_create_table_options(options)
|
64
|
+
return super unless tidb?
|
65
|
+
|
66
|
+
# AUTO_RANDOM_BASE を options 文字列の先頭に寄せ、ノイズ差分を避ける
|
67
|
+
sql_options = super
|
68
|
+
if sql_options&.include?('AUTO_RANDOM_BASE')
|
69
|
+
parts = sql_options.split(/\s+/)
|
70
|
+
auto_random_parts = parts.select { |part| part.start_with?('AUTO_RANDOM_BASE=') }
|
71
|
+
other_parts = parts.reject { |part| part.start_with?('AUTO_RANDOM_BASE=') }
|
72
|
+
sql_options = (auto_random_parts + other_parts).join(' ')
|
73
|
+
end
|
74
|
+
|
75
|
+
sql_options
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ext_tidb/version'
|
4
|
+
require_relative 'ext_tidb/detector'
|
5
|
+
require_relative 'ext_tidb/schema_creation'
|
6
|
+
require_relative 'ext_tidb/table_options'
|
7
|
+
require_relative 'ext_tidb/dump_patch'
|
8
|
+
require_relative 'ext_tidb/install'
|
9
|
+
|
10
|
+
module Ridgepole
|
11
|
+
module ExtTidb
|
12
|
+
# エントリポイント - requireで自動的にインストール
|
13
|
+
def self.setup!
|
14
|
+
Install.apply_patches!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# 自動インストール
|
20
|
+
Ridgepole::ExtTidb.setup!
|
data/ridgepole-ext-tidb.gemspec
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'lib/ridgepole/
|
3
|
+
require_relative 'lib/ridgepole/ext_tidb/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'ridgepole-ext-tidb'
|
7
|
-
spec.version = Ridgepole::
|
7
|
+
spec.version = Ridgepole::ExtTidb::VERSION
|
8
8
|
spec.authors = ['ikad']
|
9
9
|
spec.email = ['info@forgxisto.com']
|
10
10
|
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Extends Ridgepole to support TiDB's AUTO_RANDOM column attribute for seamless schema management"
|
13
13
|
spec.homepage = 'https://github.com/forgxisto/ridgepole-ext-tidb'
|
14
14
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = '>= 3.
|
15
|
+
spec.required_ruby_version = '>= 3.2.0'
|
16
16
|
|
17
17
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
18
18
|
spec.metadata['homepage_uri'] = spec.homepage
|
@@ -33,15 +33,15 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
35
|
# Dependencies
|
36
|
-
spec.add_dependency 'ridgepole'
|
36
|
+
spec.add_dependency 'ridgepole', ">= 3.0"
|
37
37
|
|
38
38
|
# Development dependencies
|
39
|
-
spec.add_development_dependency 'activerecord-trilogy-adapter'
|
40
|
-
spec.add_development_dependency 'mysql2'
|
41
39
|
spec.add_development_dependency 'rake'
|
42
40
|
spec.add_development_dependency 'rspec'
|
43
41
|
spec.add_development_dependency 'rubocop'
|
44
42
|
spec.add_development_dependency 'trilogy'
|
43
|
+
spec.add_development_dependency 'activerecord', '>= 8.0'
|
44
|
+
spec.add_development_dependency 'debug'
|
45
45
|
|
46
46
|
# Ruby 3.4+ compatibility
|
47
47
|
spec.add_development_dependency 'benchmark'
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ridgepole-ext-tidb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ikad
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-09-
|
10
|
+
date: 2025-09-08 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: ridgepole
|
@@ -15,16 +15,16 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '0'
|
18
|
+
version: '3.0'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '0'
|
25
|
+
version: '3.0'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: rake
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
30
|
- - ">="
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0'
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
41
|
+
name: rspec
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0'
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
55
|
+
name: rubocop
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - ">="
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '0'
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
|
-
name:
|
69
|
+
name: trilogy
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - ">="
|
@@ -80,21 +80,21 @@ dependencies:
|
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: '0'
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
83
|
+
name: activerecord
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: '0'
|
88
|
+
version: '8.0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - ">="
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '0'
|
95
|
+
version: '8.0'
|
96
96
|
- !ruby/object:Gem::Dependency
|
97
|
-
name:
|
97
|
+
name: debug
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - ">="
|
@@ -179,9 +179,13 @@ files:
|
|
179
179
|
- README.md
|
180
180
|
- Rakefile
|
181
181
|
- docker-compose.yml
|
182
|
-
- lib/ridgepole
|
183
|
-
- lib/ridgepole/
|
184
|
-
- lib/ridgepole/
|
182
|
+
- lib/ridgepole/ext_tidb.rb
|
183
|
+
- lib/ridgepole/ext_tidb/detector.rb
|
184
|
+
- lib/ridgepole/ext_tidb/dump_patch.rb
|
185
|
+
- lib/ridgepole/ext_tidb/install.rb
|
186
|
+
- lib/ridgepole/ext_tidb/schema_creation.rb
|
187
|
+
- lib/ridgepole/ext_tidb/table_options.rb
|
188
|
+
- lib/ridgepole/ext_tidb/version.rb
|
185
189
|
- ridgepole-ext-tidb.gemspec
|
186
190
|
homepage: https://github.com/forgxisto/ridgepole-ext-tidb
|
187
191
|
licenses:
|
@@ -198,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
202
|
requirements:
|
199
203
|
- - ">="
|
200
204
|
- !ruby/object:Gem::Version
|
201
|
-
version: 3.
|
205
|
+
version: 3.2.0
|
202
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
207
|
requirements:
|
204
208
|
- - ">="
|
data/lib/ridgepole/ext/tidb.rb
DELETED
@@ -1,266 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'tidb/version'
|
4
|
-
|
5
|
-
module Ridgepole
|
6
|
-
module Ext
|
7
|
-
module Tidb
|
8
|
-
def self.setup!
|
9
|
-
# SchemaDumperにもAUTO_RANDOM対応を追加
|
10
|
-
extend_schema_dumper
|
11
|
-
# Hash#assert_valid_keysを拡張してauto_randomキーを許可
|
12
|
-
extend_hash_assert_valid_keys
|
13
|
-
end # 手動で接続アダプタを拡張するメソッド(外部から呼び出し可能)
|
14
|
-
def self.ensure_connection_extended!
|
15
|
-
return unless ActiveRecord::Base.connected?
|
16
|
-
|
17
|
-
connection = ActiveRecord::Base.connection
|
18
|
-
extend_connection_adapter(connection)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.extend_connection_adapter(connection)
|
22
|
-
return unless connection
|
23
|
-
|
24
|
-
adapter_class = connection.class
|
25
|
-
|
26
|
-
# 既に拡張済みかチェック
|
27
|
-
return if adapter_class.method_defined?(:tidb?)
|
28
|
-
|
29
|
-
# Hash#assert_valid_keysを拡張してauto_randomキーを許可
|
30
|
-
extend_hash_assert_valid_keys
|
31
|
-
# TableDefinitionを拡張して:auto_randomオプションをサポート
|
32
|
-
extend_table_definition
|
33
|
-
|
34
|
-
adapter_class.class_eval do
|
35
|
-
# AUTO_RANDOMカラムの検出
|
36
|
-
def auto_random_column?(table_name, column_name)
|
37
|
-
return false unless tidb?
|
38
|
-
|
39
|
-
# TiDB 7.5.0でのAUTO_RANDOM検出
|
40
|
-
# SHOW CREATE TABLEでCREATE TABLE文を確認
|
41
|
-
result = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}")
|
42
|
-
create_sql = result.first[1] if result.first
|
43
|
-
|
44
|
-
if create_sql
|
45
|
-
# TiDB 7.5.0では AUTO_RANDOM がコメント形式で表示される
|
46
|
-
# 例: /*T![auto_rand] AUTO_RANDOM(5) */
|
47
|
-
if create_sql.include?('AUTO_RANDOM') || create_sql.include?('auto_rand')
|
48
|
-
return true
|
49
|
-
end
|
50
|
-
|
51
|
-
# テーブルオプションにAUTO_RANDOM_BASEが含まれているかチェック
|
52
|
-
if create_sql.include?('AUTO_RANDOM_BASE')
|
53
|
-
return true
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# INFORMATION_SCHEMA.COLUMNS の EXTRA を確認(フォールバック)
|
58
|
-
extra = select_value(<<~SQL)
|
59
|
-
SELECT EXTRA
|
60
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
61
|
-
WHERE TABLE_SCHEMA = DATABASE()
|
62
|
-
AND TABLE_NAME = #{quote(table_name)}
|
63
|
-
AND COLUMN_NAME = #{quote(column_name)}
|
64
|
-
SQL
|
65
|
-
|
66
|
-
if extra&.downcase&.include?('auto_random')
|
67
|
-
return true
|
68
|
-
end
|
69
|
-
|
70
|
-
false
|
71
|
-
rescue => e
|
72
|
-
puts "AUTO_RANDOM detection failed: #{e.message}"
|
73
|
-
false
|
74
|
-
end
|
75
|
-
|
76
|
-
# TiDBかどうかの判定
|
77
|
-
def tidb?
|
78
|
-
# VERSION()関数でTiDBを検出(キャッシュなし)
|
79
|
-
version_info = select_value('SELECT VERSION()')
|
80
|
-
result = version_info&.include?('TiDB') == true
|
81
|
-
Rails.logger.debug "TiDB detection: version=#{version_info}, result=#{result}" if defined?(Rails)
|
82
|
-
result
|
83
|
-
rescue => e
|
84
|
-
Rails.logger.debug "TiDB detection failed: #{e.message}" if defined?(Rails)
|
85
|
-
false
|
86
|
-
end
|
87
|
-
|
88
|
-
# CREATE TABLE時のAUTO_RANDOM対応
|
89
|
-
alias_method :create_table_without_auto_random, :create_table
|
90
|
-
def create_table(table_name, **options, &block)
|
91
|
-
# :auto_randomキーを処理する前に、idオプションから取り除く
|
92
|
-
if options.dig(:id, :auto_random) && tidb?
|
93
|
-
# auto_randomフラグを保存
|
94
|
-
auto_random_enabled = options[:id].delete(:auto_random)
|
95
|
-
|
96
|
-
# 通常のcreate_tableを呼び出してテーブル構造を作成
|
97
|
-
create_table_without_auto_random(table_name, **options, &block)
|
98
|
-
|
99
|
-
# AUTO_RANDOMを有効にするためにALTER TABLEを実行
|
100
|
-
if auto_random_enabled
|
101
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} MODIFY COLUMN id BIGINT AUTO_RANDOM PRIMARY KEY")
|
102
|
-
end
|
103
|
-
else
|
104
|
-
create_table_without_auto_random(table_name, **options, &block)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
puts "✅ Methods added to #{adapter_class}"
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.extend_activerecord_adapters
|
113
|
-
puts "📦 Extending ActiveRecord adapters..."
|
114
|
-
# Hash#assert_valid_keysを拡張してauto_randomキーを許可
|
115
|
-
extend_hash_assert_valid_keys
|
116
|
-
# TableDefinitionを拡張して:auto_randomオプションをサポート
|
117
|
-
extend_table_definition
|
118
|
-
|
119
|
-
# MySQL系アダプタにAUTO_RANDOMサポートを追加
|
120
|
-
extend_adapter('ActiveRecord::ConnectionAdapters::Mysql2Adapter')
|
121
|
-
extend_adapter('ActiveRecord::ConnectionAdapters::TrilogyAdapter')
|
122
|
-
|
123
|
-
# SchemaDumperにもAUTO_RANDOM対応を追加
|
124
|
-
extend_schema_dumper
|
125
|
-
puts "📦 Adapter extension complete"
|
126
|
-
end
|
127
|
-
|
128
|
-
def self.extend_adapter(adapter_name)
|
129
|
-
return unless defined?(ActiveRecord::ConnectionAdapters)
|
130
|
-
|
131
|
-
begin
|
132
|
-
adapter_class = Object.const_get(adapter_name)
|
133
|
-
puts "🔧 Extending #{adapter_name}..."
|
134
|
-
rescue NameError => e
|
135
|
-
# アダプタが利用できない場合はスキップ
|
136
|
-
puts "⚠️ Skipping #{adapter_name}: #{e.message}"
|
137
|
-
return
|
138
|
-
end
|
139
|
-
|
140
|
-
# Hash#assert_valid_keysを拡張してauto_randomキーを許可
|
141
|
-
extend_hash_assert_valid_keys
|
142
|
-
# TableDefinitionを拡張して:auto_randomオプションをサポート
|
143
|
-
extend_table_definition
|
144
|
-
|
145
|
-
# 一時的にputsを外して動作確認
|
146
|
-
adapter_class.class_eval do
|
147
|
-
# AUTO_RANDOMカラムの検出
|
148
|
-
def auto_random_column?(table_name, column_name)
|
149
|
-
return false unless tidb?
|
150
|
-
|
151
|
-
extra = select_value(<<~SQL)
|
152
|
-
SELECT EXTRA
|
153
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
154
|
-
WHERE TABLE_SCHEMA = DATABASE()
|
155
|
-
AND TABLE_NAME = #{quote(table_name)}
|
156
|
-
AND COLUMN_NAME = #{quote(column_name)}
|
157
|
-
SQL
|
158
|
-
|
159
|
-
extra&.downcase&.include?('auto_random') == true
|
160
|
-
rescue
|
161
|
-
false
|
162
|
-
end
|
163
|
-
|
164
|
-
# TiDBかどうかの判定
|
165
|
-
def tidb?
|
166
|
-
# VERSION()関数でTiDBを検出(キャッシュなし)
|
167
|
-
version_info = select_value('SELECT VERSION()')
|
168
|
-
result = version_info&.include?('TiDB') == true
|
169
|
-
Rails.logger.debug "TiDB detection: version=#{version_info}, result=#{result}" if defined?(Rails)
|
170
|
-
result
|
171
|
-
rescue => e
|
172
|
-
Rails.logger.debug "TiDB detection failed: #{e.message}" if defined?(Rails)
|
173
|
-
false
|
174
|
-
end
|
175
|
-
|
176
|
-
# CREATE TABLE時のAUTO_RANDOM対応
|
177
|
-
alias_method :create_table_without_auto_random, :create_table
|
178
|
-
def create_table(table_name, **options, &block)
|
179
|
-
# :auto_randomキーを処理する前に、idオプションから取り除く
|
180
|
-
if options.dig(:id, :auto_random) && tidb?
|
181
|
-
# auto_randomフラグを保存
|
182
|
-
auto_random_enabled = options[:id].delete(:auto_random)
|
183
|
-
|
184
|
-
# 通常のcreate_tableを呼び出してテーブル構造を作成
|
185
|
-
create_table_without_auto_random(table_name, **options, &block)
|
186
|
-
|
187
|
-
# AUTO_RANDOMを有効にするためにALTER TABLEを実行
|
188
|
-
if auto_random_enabled
|
189
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} MODIFY COLUMN id BIGINT AUTO_RANDOM PRIMARY KEY")
|
190
|
-
end
|
191
|
-
else
|
192
|
-
create_table_without_auto_random(table_name, **options, &block)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
puts "✅ Methods added to #{adapter_name}"
|
198
|
-
end
|
199
|
-
|
200
|
-
def self.extend_schema_dumper
|
201
|
-
return unless defined?(ActiveRecord::SchemaDumper)
|
202
|
-
|
203
|
-
ActiveRecord::SchemaDumper.class_eval do
|
204
|
-
alias_method :prepare_column_options_without_auto_random, :prepare_column_options
|
205
|
-
def prepare_column_options(column)
|
206
|
-
spec = prepare_column_options_without_auto_random(column)
|
207
|
-
|
208
|
-
# TiDB接続でAUTO_RANDOMカラムの場合、auto_randomオプションを追加
|
209
|
-
if @connection.respond_to?(:tidb?) && @connection.tidb? &&
|
210
|
-
@connection.respond_to?(:auto_random_column?) &&
|
211
|
-
@connection.auto_random_column?(@table, column.name)
|
212
|
-
spec[:auto_random] = true
|
213
|
-
end
|
214
|
-
|
215
|
-
spec
|
216
|
-
end
|
217
|
-
end
|
218
|
-
rescue NameError
|
219
|
-
# SchemaDumperが利用できない場合はスキップ
|
220
|
-
end
|
221
|
-
|
222
|
-
def self.extend_table_definition
|
223
|
-
return unless defined?(ActiveRecord::ConnectionAdapters::TableDefinition)
|
224
|
-
|
225
|
-
# TableDefinitionを拡張して:auto_randomオプションをサポート
|
226
|
-
ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
|
227
|
-
# カラム作成時のオプション検証を拡張
|
228
|
-
alias_method :column_without_auto_random, :column
|
229
|
-
def column(name, type, **options)
|
230
|
-
# :auto_randomオプションが含まれている場合は、それを取り除いて後で処理
|
231
|
-
if options.key?(:auto_random)
|
232
|
-
auto_random_value = options.delete(:auto_random)
|
233
|
-
# カラム定義にauto_randomの情報を保存(後でcreate_tableで使用)
|
234
|
-
@auto_random_columns ||= {}
|
235
|
-
@auto_random_columns[name.to_s] = auto_random_value
|
236
|
-
end
|
237
|
-
column_without_auto_random(name, type, **options)
|
238
|
-
end
|
239
|
-
|
240
|
-
# auto_randomカラムの情報を取得するメソッド
|
241
|
-
def auto_random_columns
|
242
|
-
@auto_random_columns ||= {}
|
243
|
-
end
|
244
|
-
end
|
245
|
-
rescue NameError => e
|
246
|
-
puts "⚠️ Could not extend TableDefinition: #{e.message}"
|
247
|
-
end
|
248
|
-
|
249
|
-
def self.extend_hash_assert_valid_keys
|
250
|
-
# Hashクラスを拡張して、auto_randomキーを有効なキーとして認識させる
|
251
|
-
Hash.class_eval do
|
252
|
-
alias_method :assert_valid_keys_without_auto_random, :assert_valid_keys
|
253
|
-
def assert_valid_keys(*valid_keys)
|
254
|
-
# auto_randomキーが含まれている場合は、それを有効なキーとして追加
|
255
|
-
if keys.include?(:auto_random) && !valid_keys.include?(:auto_random)
|
256
|
-
valid_keys = valid_keys + [:auto_random]
|
257
|
-
end
|
258
|
-
assert_valid_keys_without_auto_random(*valid_keys)
|
259
|
-
end
|
260
|
-
end
|
261
|
-
rescue NameError => e
|
262
|
-
puts "⚠️ Could not extend Hash#assert_valid_keys: #{e.message}"
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
data/lib/ridgepole-ext-tidb.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'ridgepole/ext/tidb'
|
4
|
-
|
5
|
-
# Ridgepoleが使用される前に拡張を適用
|
6
|
-
Ridgepole::Ext::Tidb.setup!
|
7
|
-
|
8
|
-
# ActiveRecordアダプタが利用可能になったときに自動拡張を実行
|
9
|
-
if defined?(ActiveRecord::Base)
|
10
|
-
Ridgepole::Ext::Tidb.extend_activerecord_adapters
|
11
|
-
else
|
12
|
-
# ActiveRecordが後でロードされる場合に備えてフックを設定
|
13
|
-
ActiveSupport.on_load(:active_record) do
|
14
|
-
Ridgepole::Ext::Tidb.extend_activerecord_adapters
|
15
|
-
end
|
16
|
-
end
|