ridgepole-ext-tidb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +63 -0
- data/Dockerfile +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +212 -0
- data/Rakefile +12 -0
- data/docker-compose.yml +36 -0
- data/lib/ridgepole/ext/tidb/connection_adapters.rb +183 -0
- data/lib/ridgepole/ext/tidb/schema_dumper.rb +76 -0
- data/lib/ridgepole/ext/tidb/version.rb +9 -0
- data/lib/ridgepole/ext/tidb.rb +45 -0
- data/lib/ridgepole-ext-tidb.rb +3 -0
- data/ridgepole-ext-tidb.gemspec +50 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba5b2121e9d9b95fadbaacebb618d89855c9de0a55240ec56fb9bb992c678e95
|
4
|
+
data.tar.gz: f208857ea6b30710b2b09f20ce21358ce050d6d6f2d10704b895f90acc3ced42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac287e753371f6056334b07869dc97dc1af6cbc4560241d16e0c09f35108c75b2f945bf04d43621f8545a025bf8da8ebacfc17faaec888dce956acd6bf69d338
|
7
|
+
data.tar.gz: f3feca6ac36a33aa4a1a08b621515614997b187a6f90834b9e2d886169a607cf36f8298a9812ade8201ec78f797ab62dc38e4a59b468bbdde4168b3f458de03c
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Initial implementation of TiDB AUTO_RANDOM support for Ridgepole
|
12
|
+
- Schema dumper extension for AUTO_RANDOM columns
|
13
|
+
- Connection adapter extensions for TiDB compatibility
|
14
|
+
- Comprehensive test suite
|
15
|
+
|
16
|
+
## [0.1.0] - 2025-09-05
|
17
|
+
|
18
|
+
### Added
|
19
|
+
- Initial release
|
20
|
+
- TiDB AUTO_RANDOM column support
|
21
|
+
- Ridgepole integration
|
data/CLAUDE.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Project Overview
|
6
|
+
|
7
|
+
ridgepole-ext-tidb is a Ruby gem that extends Ridgepole to support TiDB's AUTO_RANDOM column attribute for distributed ID generation. It integrates with the trilogy adapter and extends ActiveRecord's schema management.
|
8
|
+
|
9
|
+
## Development Commands
|
10
|
+
|
11
|
+
### Setup
|
12
|
+
```bash
|
13
|
+
bundle install
|
14
|
+
```
|
15
|
+
|
16
|
+
### Testing
|
17
|
+
```bash
|
18
|
+
# Basic tests without TiDB
|
19
|
+
SKIP_TIDB_TESTS=1 bundle exec rspec
|
20
|
+
|
21
|
+
# Full integration tests (requires Docker)
|
22
|
+
docker-compose up -d
|
23
|
+
bundle exec rspec
|
24
|
+
|
25
|
+
# Docker-based testing
|
26
|
+
docker-compose run --rm test
|
27
|
+
|
28
|
+
# Run single test file
|
29
|
+
bundle exec rspec spec/ridgepole/ext/tidb_spec.rb
|
30
|
+
```
|
31
|
+
|
32
|
+
### Code Quality
|
33
|
+
```bash
|
34
|
+
bundle exec rubocop
|
35
|
+
bundle exec rake # runs spec + rubocop
|
36
|
+
```
|
37
|
+
|
38
|
+
### TiDB Environment
|
39
|
+
```bash
|
40
|
+
docker-compose up -d tidb
|
41
|
+
docker-compose exec tidb mysql -u root -h 127.0.0.1 -P 4000 -e "CREATE DATABASE IF NOT EXISTS ridgepole_test"
|
42
|
+
```
|
43
|
+
|
44
|
+
## Architecture
|
45
|
+
|
46
|
+
### Extension Pattern
|
47
|
+
The gem uses module prepending/including to extend existing ActiveRecord functionality:
|
48
|
+
- `setup!` method loads trilogy adapter and injects modules
|
49
|
+
- `SchemaDumper` module prepended to ActiveRecord::SchemaDumper
|
50
|
+
- `TrilogyAdapter` module included in AbstractMysqlAdapter
|
51
|
+
|
52
|
+
### Core Components
|
53
|
+
- **Main Extension** (`lib/ridgepole/ext/tidb.rb`): Entry point and setup
|
54
|
+
- **Schema Dumper** (`lib/ridgepole/ext/tidb/schema_dumper.rb`): Detects and dumps AUTO_RANDOM attributes by querying INFORMATION_SCHEMA.COLUMNS
|
55
|
+
- **Connection Adapters** (`lib/ridgepole/ext/tidb/connection_adapters.rb`): Adds TiDB detection and AUTO_RANDOM column support
|
56
|
+
|
57
|
+
### TiDB Integration
|
58
|
+
- Detects TiDB by querying `@@tidb_version` or version strings
|
59
|
+
- AUTO_RANDOM columns identified via INFORMATION_SCHEMA queries
|
60
|
+
- Modifies column creation to add AUTO_RANDOM attribute (requires BIGINT PRIMARY KEY)
|
61
|
+
|
62
|
+
### Test Configuration
|
63
|
+
Uses trilogy adapter connecting to TiDB on port 4000. Supports mock TiDB behavior for development and automatic test cleanup.
|
data/Dockerfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
FROM ruby:3.1
|
2
|
+
|
3
|
+
WORKDIR /app
|
4
|
+
|
5
|
+
# Install system dependencies for mysql2
|
6
|
+
RUN apt-get update -qq && \
|
7
|
+
apt-get install -y build-essential default-mysql-client default-libmysqlclient-dev \
|
8
|
+
pkg-config libssl-dev zlib1g-dev && \
|
9
|
+
rm -rf /var/lib/apt/lists/*
|
10
|
+
|
11
|
+
# Install dependencies
|
12
|
+
COPY Gemfile Gemfile.lock ridgepole-ext-tidb.gemspec ./
|
13
|
+
COPY lib/ridgepole/ext/tidb/version.rb lib/ridgepole/ext/tidb/
|
14
|
+
|
15
|
+
RUN bundle install
|
16
|
+
|
17
|
+
COPY . .
|
18
|
+
|
19
|
+
CMD ["bundle", "exec", "rspec"]
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Forgxisto.inc
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
# Ridgepole::Ext::Tidb
|
2
|
+
|
3
|
+

|
4
|
+

|
5
|
+

|
6
|
+
|
7
|
+
TiDBの`AUTO_RANDOM`カラム属性をサポートするRidgepole拡張機能です。この拡張により、TiDBの分散IDジェネレーション機能をSchemafile管理に統合できます。
|
8
|
+
|
9
|
+
## 主な機能
|
10
|
+
|
11
|
+
- **AUTO_RANDOM検出**: TiDBのAUTO_RANDOMカラムを自動検出
|
12
|
+
- **スキーマダンプ対応**: AUTO_RANDOM属性をRidgefileに正確に出力
|
13
|
+
- **冪等性保証**: スキーマ適用の際の差分を正確に計算
|
14
|
+
- **trilogy アダプター対応**: mysql2の代わりにtrilogyを使用
|
15
|
+
- **Ruby 3.4+ 対応**: 最新のRubyバージョンに完全対応
|
16
|
+
|
17
|
+
## インストール
|
18
|
+
|
19
|
+
Gemfileに以下を追加してください:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'ridgepole-ext-tidb'
|
23
|
+
```
|
24
|
+
|
25
|
+
そして以下を実行:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
$ bundle install
|
29
|
+
```
|
30
|
+
|
31
|
+
または直接インストール:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
$ gem install ridgepole-ext-tidb
|
35
|
+
```
|
36
|
+
|
37
|
+
## 使用方法
|
38
|
+
|
39
|
+
### 基本設定
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'ridgepole'
|
43
|
+
require 'ridgepole/ext/tidb'
|
44
|
+
|
45
|
+
# ActiveRecord読み込み後にTiDB拡張をセットアップ
|
46
|
+
Ridgepole::Ext::Tidb.setup!
|
47
|
+
|
48
|
+
# Ridgepoleクライアントを設定
|
49
|
+
client = Ridgepole::Client.new({
|
50
|
+
adapter: 'trilogy', # trilogy アダプターを使用
|
51
|
+
host: 'localhost',
|
52
|
+
port: 4000,
|
53
|
+
username: 'root',
|
54
|
+
password: '',
|
55
|
+
database: 'your_database'
|
56
|
+
})
|
57
|
+
```
|
58
|
+
|
59
|
+
### Schemafileでの使用
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# Schemafile
|
63
|
+
create_table "users", id: { type: :bigint, auto_random: true } do |t|
|
64
|
+
t.string :name, null: false
|
65
|
+
t.string :email, null: false
|
66
|
+
t.timestamps
|
67
|
+
end
|
68
|
+
|
69
|
+
create_table "posts", force: :cascade do |t|
|
70
|
+
t.bigint :id, auto_random: true, primary_key: true
|
71
|
+
t.bigint :user_id, null: false
|
72
|
+
t.string :title, null: false
|
73
|
+
t.text :content
|
74
|
+
t.timestamps
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### CLI使用例
|
79
|
+
|
80
|
+
環境変数またはdatabase.ymlファイルを用意してから実行:
|
81
|
+
|
82
|
+
```bash
|
83
|
+
# 環境変数で設定
|
84
|
+
export TIDB_HOST=localhost
|
85
|
+
export TIDB_PORT=4000
|
86
|
+
export TIDB_USER=root
|
87
|
+
export TIDB_PASSWORD=""
|
88
|
+
export TIDB_DATABASE=your_database
|
89
|
+
|
90
|
+
# スキーマを適用
|
91
|
+
$ bundle exec ridgepole -c config/database.yml -E development -f Schemafile --apply
|
92
|
+
|
93
|
+
# ドライランで確認
|
94
|
+
$ bundle exec ridgepole -c config/database.yml -E development -f Schemafile --apply --dry-run
|
95
|
+
|
96
|
+
# スキーマをダンプ
|
97
|
+
$ bundle exec ridgepole -c config/database.yml -E development --export -o Schemafile
|
98
|
+
```
|
99
|
+
|
100
|
+
## データベース設定
|
101
|
+
|
102
|
+
### database.yml例
|
103
|
+
|
104
|
+
プロジェクトルートに `config/database.yml` を作成:
|
105
|
+
|
106
|
+
```yaml
|
107
|
+
development:
|
108
|
+
adapter: trilogy
|
109
|
+
host: <%= ENV['TIDB_HOST'] || 'localhost' %>
|
110
|
+
port: <%= ENV['TIDB_PORT'] || 4000 %>
|
111
|
+
username: <%= ENV['TIDB_USER'] || 'root' %>
|
112
|
+
password: <%= ENV['TIDB_PASSWORD'] || '' %>
|
113
|
+
database: <%= ENV['TIDB_DATABASE'] || 'your_app_development' %>
|
114
|
+
encoding: utf8mb4
|
115
|
+
collation: utf8mb4_unicode_ci
|
116
|
+
|
117
|
+
test:
|
118
|
+
adapter: trilogy
|
119
|
+
host: <%= ENV['TIDB_HOST'] || 'localhost' %>
|
120
|
+
port: <%= ENV['TIDB_PORT'] || 4000 %>
|
121
|
+
username: <%= ENV['TIDB_USER'] || 'root' %>
|
122
|
+
password: <%= ENV['TIDB_PASSWORD'] || '' %>
|
123
|
+
database: <%= ENV['TIDB_DATABASE'] || 'your_app_test' %>
|
124
|
+
encoding: utf8mb4
|
125
|
+
collation: utf8mb4_unicode_ci
|
126
|
+
```
|
127
|
+
|
128
|
+
## TiDB AUTO_RANDOMについて
|
129
|
+
|
130
|
+
TiDBの`AUTO_RANDOM`は、分散環境での重複しないID生成機能です:
|
131
|
+
|
132
|
+
- **ホットスポット回避**: 順番IDによるホットスポットを防ぐ
|
133
|
+
- **高性能**: 挿入パフォーマンスが向上
|
134
|
+
- **スケーラビリティ**: 水平スケーリングに適している
|
135
|
+
|
136
|
+
### AUTO_RANDOMの仕組み
|
137
|
+
|
138
|
+
```sql
|
139
|
+
-- TiDBでのAUTO_RANDOMテーブル例
|
140
|
+
CREATE TABLE users (
|
141
|
+
id BIGINT PRIMARY KEY AUTO_RANDOM,
|
142
|
+
name VARCHAR(100) NOT NULL
|
143
|
+
);
|
144
|
+
```
|
145
|
+
|
146
|
+
このテーブルにデータを挿入すると、IDは自動的にランダムな値が割り当てられます。
|
147
|
+
|
148
|
+
## 開発
|
149
|
+
|
150
|
+
### 前提条件
|
151
|
+
|
152
|
+
- Ruby 3.1 以上
|
153
|
+
- TiDB 4.0 以上 (テスト用)
|
154
|
+
- Docker (テスト環境用)
|
155
|
+
|
156
|
+
### セットアップ
|
157
|
+
|
158
|
+
```bash
|
159
|
+
$ git clone https://github.com/forgxisto/ridgepole-ext-tidb.git
|
160
|
+
$ cd ridgepole-ext-tidb
|
161
|
+
$ bundle install
|
162
|
+
```
|
163
|
+
|
164
|
+
### テスト実行
|
165
|
+
|
166
|
+
```bash
|
167
|
+
# 基本機能テスト(TiDBなしでも実行可能)
|
168
|
+
$ SKIP_TIDB_TESTS=1 bundle exec rspec
|
169
|
+
|
170
|
+
# TiDB統合テスト(Dockerが必要)
|
171
|
+
$ docker compose up -d
|
172
|
+
$ bundle exec rspec
|
173
|
+
|
174
|
+
# Docker環境でのテスト
|
175
|
+
$ docker compose run --rm test
|
176
|
+
```
|
177
|
+
|
178
|
+
### TiDBテスト環境
|
179
|
+
|
180
|
+
```bash
|
181
|
+
# TiDBサービス起動
|
182
|
+
$ docker compose up -d tidb
|
183
|
+
|
184
|
+
# テスト用データベース作成
|
185
|
+
$ docker compose exec tidb mysql -u root -h 127.0.0.1 -P 4000 -e "CREATE DATABASE IF NOT EXISTS ridgepole_test"
|
186
|
+
|
187
|
+
# テスト実行
|
188
|
+
$ bundle exec rspec
|
189
|
+
```
|
190
|
+
|
191
|
+
## Contributing
|
192
|
+
|
193
|
+
1. このリポジトリをフォーク
|
194
|
+
2. フィーチャーブランチを作成 (`git checkout -b my-new-feature`)
|
195
|
+
3. 変更をコミット (`git commit -am 'Add some feature'`)
|
196
|
+
4. ブランチにプッシュ (`git push origin my-new-feature`)
|
197
|
+
5. プルリクエストを作成
|
198
|
+
|
199
|
+
## ライセンス
|
200
|
+
|
201
|
+
このgemは[MIT License](LICENSE.txt)の下でオープンソースとして利用可能です。
|
202
|
+
|
203
|
+
## 参考リンク
|
204
|
+
|
205
|
+
- [Ridgepole](https://github.com/ridgepole/ridgepole) - スキーマ管理ツール
|
206
|
+
- [TiDB](https://github.com/pingcap/tidb) - 分散SQLデータベース
|
207
|
+
- [TiDB AUTO_RANDOM](https://docs.pingcap.com/tidb/stable/auto-random) - AUTO_RANDOMドキュメント
|
208
|
+
- [trilogy](https://github.com/trilogy-libraries/trilogy) - MySQLクライアントライブラリ
|
209
|
+
|
210
|
+
## サポート
|
211
|
+
|
212
|
+
問題や質問がある場合は、[Issues](https://github.com/forgxisto/ridgepole-ext-tidb/issues)にて報告してください。
|
data/Rakefile
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
version: '3.8'
|
2
|
+
|
3
|
+
services:
|
4
|
+
tidb:
|
5
|
+
image: pingcap/tidb:latest
|
6
|
+
container_name: ridgepole-tidb-test
|
7
|
+
ports:
|
8
|
+
- "14000:4000"
|
9
|
+
- "14080:10080"
|
10
|
+
environment:
|
11
|
+
- MYSQL_ROOT_PASSWORD=
|
12
|
+
command:
|
13
|
+
- --store=mocktikv
|
14
|
+
- --host=0.0.0.0
|
15
|
+
- --advertise-address=0.0.0.0
|
16
|
+
healthcheck:
|
17
|
+
test: ["CMD", "sh", "-c", "nc -z 127.0.0.1 4000 || exit 1"]
|
18
|
+
interval: 10s
|
19
|
+
timeout: 5s
|
20
|
+
retries: 5
|
21
|
+
start_period: 30s
|
22
|
+
|
23
|
+
test:
|
24
|
+
build: .
|
25
|
+
depends_on:
|
26
|
+
- tidb
|
27
|
+
environment:
|
28
|
+
- TIDB_HOST=tidb
|
29
|
+
- TIDB_PORT=4000
|
30
|
+
- TIDB_USER=root
|
31
|
+
- TIDB_PASSWORD=
|
32
|
+
- TIDB_DATABASE=ridgepole_test
|
33
|
+
working_dir: /app
|
34
|
+
volumes:
|
35
|
+
- .:/app
|
36
|
+
command: bundle exec rspec
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module Ext
|
5
|
+
module Tidb
|
6
|
+
module ConnectionAdapters
|
7
|
+
module TrilogyAdapter
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Check if this is a TiDB connection (class method)
|
14
|
+
def tidb?
|
15
|
+
@tidb ||= detect_tidb_connection
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def detect_tidb_connection
|
21
|
+
# Try TiDB-specific query first
|
22
|
+
begin
|
23
|
+
result = select_value('SELECT @@tidb_version')
|
24
|
+
return true if result && !result.empty?
|
25
|
+
rescue StandardError
|
26
|
+
# Not a TiDB or query failed, continue
|
27
|
+
end
|
28
|
+
|
29
|
+
# Try version string detection
|
30
|
+
begin
|
31
|
+
result = select_value('SELECT VERSION()')
|
32
|
+
return true if result&.include?('TiDB')
|
33
|
+
rescue StandardError
|
34
|
+
# Version query failed, continue
|
35
|
+
end
|
36
|
+
|
37
|
+
# Try variables detection
|
38
|
+
begin
|
39
|
+
result = select_value("SHOW VARIABLES LIKE 'version_comment'")
|
40
|
+
return true if result&.include?('TiDB')
|
41
|
+
rescue StandardError
|
42
|
+
# Variables query failed
|
43
|
+
end
|
44
|
+
|
45
|
+
false
|
46
|
+
rescue StandardError => e
|
47
|
+
# Log error if logger available
|
48
|
+
warn "TiDB detection failed: #{e.message}" if defined?(Rails) && Rails.logger
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Check if this is a TiDB connection (instance method)
|
54
|
+
def tidb?
|
55
|
+
@tidb ||= self.class.tidb?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Override create_table to support auto_random option in column definitions
|
59
|
+
def create_table(table_name, id: :default, primary_key: nil, force: nil, **options, &block)
|
60
|
+
# Extract id column options to check for auto_random
|
61
|
+
if id.is_a?(Hash) && id[:auto_random] && tidb?
|
62
|
+
# Convert id options for TiDB AUTO_RANDOM
|
63
|
+
id = convert_id_options_for_tidb(id)
|
64
|
+
end
|
65
|
+
|
66
|
+
super(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Override add_column to support auto_random option
|
70
|
+
def add_column(table_name, column_name, type, **options)
|
71
|
+
auto_random = options.delete(:auto_random)
|
72
|
+
|
73
|
+
if auto_random && tidb?
|
74
|
+
# Validate AUTO_RANDOM constraints
|
75
|
+
validate_auto_random_constraints!(table_name, column_name, type, options)
|
76
|
+
|
77
|
+
# Call super first to create the basic column
|
78
|
+
super(table_name, column_name, type, **options)
|
79
|
+
|
80
|
+
# Then modify it to add AUTO_RANDOM
|
81
|
+
modify_column_for_auto_random(table_name, column_name, type, options)
|
82
|
+
else
|
83
|
+
super(table_name, column_name, type, **options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Check if a column has AUTO_RANDOM attribute
|
88
|
+
def auto_random_column?(table_name, column_name)
|
89
|
+
return false unless tidb?
|
90
|
+
|
91
|
+
sql = <<~SQL
|
92
|
+
SELECT EXTRA
|
93
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
94
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
95
|
+
AND TABLE_NAME = #{quote(table_name.to_s)}
|
96
|
+
AND COLUMN_NAME = #{quote(column_name.to_s)}
|
97
|
+
SQL
|
98
|
+
|
99
|
+
result = select_value(sql)
|
100
|
+
return false unless result
|
101
|
+
|
102
|
+
result.downcase.include?('auto_random')
|
103
|
+
rescue StandardError => e
|
104
|
+
warn "Failed to check AUTO_RANDOM attribute: #{e.message}" if defined?(Rails) && Rails.logger
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def convert_id_options_for_tidb(id_options)
|
111
|
+
# Ensure proper type for AUTO_RANDOM
|
112
|
+
id_options = id_options.dup
|
113
|
+
id_options[:type] ||= :bigint
|
114
|
+
|
115
|
+
# AUTO_RANDOM requires bigint type
|
116
|
+
unless %i[bigint integer].include?(id_options[:type])
|
117
|
+
raise Ridgepole::Ext::Tidb::AutoRandomConstraintError,
|
118
|
+
"AUTO_RANDOM requires :bigint or :integer type, got #{id_options[:type]}"
|
119
|
+
end
|
120
|
+
|
121
|
+
id_options
|
122
|
+
end
|
123
|
+
|
124
|
+
def modify_column_for_auto_random(table_name, column_name, type, _options)
|
125
|
+
# Build the SQL type string
|
126
|
+
sql_type = case type
|
127
|
+
when :bigint then 'BIGINT'
|
128
|
+
when :integer then 'INT'
|
129
|
+
else
|
130
|
+
raise Ridgepole::Ext::Tidb::UnsupportedFeatureError,
|
131
|
+
"Unsupported type for AUTO_RANDOM: #{type}"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Add AUTO_RANDOM and PRIMARY KEY
|
135
|
+
alter_sql = "ALTER TABLE #{quote_table_name(table_name)} " \
|
136
|
+
"MODIFY COLUMN #{quote_column_name(column_name)} " \
|
137
|
+
"#{sql_type} AUTO_RANDOM PRIMARY KEY"
|
138
|
+
|
139
|
+
begin
|
140
|
+
execute(alter_sql)
|
141
|
+
rescue StandardError => e
|
142
|
+
# Re-raise with more context
|
143
|
+
raise Ridgepole::Ext::Tidb::Error,
|
144
|
+
"Failed to add AUTO_RANDOM attribute to #{table_name}.#{column_name}: #{e.message}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def validate_auto_random_constraints!(table_name, _column_name, type, options)
|
149
|
+
# AUTO_RANDOM must be on a bigint or int column
|
150
|
+
unless %i[bigint integer].include?(type)
|
151
|
+
raise Ridgepole::Ext::Tidb::AutoRandomConstraintError,
|
152
|
+
"AUTO_RANDOM requires :bigint or :integer column type, got #{type}"
|
153
|
+
end
|
154
|
+
|
155
|
+
# AUTO_RANDOM must be primary key
|
156
|
+
if options[:primary_key] == false
|
157
|
+
raise Ridgepole::Ext::Tidb::AutoRandomConstraintError,
|
158
|
+
'AUTO_RANDOM column must be a primary key'
|
159
|
+
end
|
160
|
+
|
161
|
+
# Check if table already has a primary key (only if table exists)
|
162
|
+
return unless table_exists?(table_name)
|
163
|
+
|
164
|
+
begin
|
165
|
+
existing_pk = primary_keys(table_name)
|
166
|
+
unless existing_pk.empty?
|
167
|
+
raise Ridgepole::Ext::Tidb::AutoRandomConstraintError,
|
168
|
+
"Cannot add AUTO_RANDOM column to table with existing primary key: #{existing_pk.join(', ')}"
|
169
|
+
end
|
170
|
+
rescue NoMethodError
|
171
|
+
# primary_keys method doesn't exist - skip check
|
172
|
+
rescue StandardError => e
|
173
|
+
# Only log database-related errors, re-raise validation errors
|
174
|
+
raise if e.is_a?(Ridgepole::Ext::Tidb::AutoRandomConstraintError)
|
175
|
+
|
176
|
+
warn "Warning: Could not verify primary key constraints: #{e.message}" if defined?(Rails) && Rails.logger
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ridgepole
|
4
|
+
module Ext
|
5
|
+
module Tidb
|
6
|
+
module SchemaDumper
|
7
|
+
# Override prepare_column_options to include auto_random
|
8
|
+
def prepare_column_options(column)
|
9
|
+
spec = super(column)
|
10
|
+
|
11
|
+
# Add auto_random if this column has the attribute
|
12
|
+
spec[:auto_random] = true if tidb_connection? && auto_random_column?(current_table_name, column.name)
|
13
|
+
|
14
|
+
spec
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def tidb_connection?
|
20
|
+
connection.respond_to?(:tidb?) && connection.tidb?
|
21
|
+
rescue StandardError
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_table_name
|
26
|
+
# Get the current table name being processed
|
27
|
+
# In ActiveRecord schema dumper context, @table holds the current table name
|
28
|
+
return @table.to_s if instance_variable_defined?(:@table) && @table
|
29
|
+
|
30
|
+
# Fallback: try to extract from the column if it has table information
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def auto_random_column?(table_name, column_name)
|
35
|
+
return false unless table_name && column_name
|
36
|
+
return false unless tidb_connection?
|
37
|
+
|
38
|
+
# Use the connection's auto_random_column? method if available
|
39
|
+
return connection.auto_random_column?(table_name, column_name) if connection.respond_to?(:auto_random_column?)
|
40
|
+
|
41
|
+
# Fallback: direct query
|
42
|
+
check_auto_random_attribute(table_name, column_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_auto_random_attribute(table_name, column_name)
|
46
|
+
sql = <<~SQL
|
47
|
+
SELECT EXTRA
|
48
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
49
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
50
|
+
AND TABLE_NAME = #{connection.quote(table_name.to_s)}
|
51
|
+
AND COLUMN_NAME = #{connection.quote(column_name.to_s)}
|
52
|
+
SQL
|
53
|
+
|
54
|
+
result = connection.select_value(sql)
|
55
|
+
return false unless result
|
56
|
+
|
57
|
+
result.downcase.include?('auto_random')
|
58
|
+
rescue StandardError => e
|
59
|
+
# Log warning if possible
|
60
|
+
if defined?(Rails) && Rails.logger
|
61
|
+
warn "Failed to check AUTO_RANDOM attribute for #{table_name}.#{column_name}: #{e.message}"
|
62
|
+
end
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Override table method to track current table name
|
67
|
+
def table(table_name, stream)
|
68
|
+
@table = table_name
|
69
|
+
super(table_name, stream)
|
70
|
+
ensure
|
71
|
+
@table = nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'tidb/version'
|
4
|
+
|
5
|
+
module Ridgepole
|
6
|
+
module Ext
|
7
|
+
module Tidb
|
8
|
+
class Error < StandardError; end
|
9
|
+
class TidbConnectionError < Error; end
|
10
|
+
class AutoRandomConstraintError < Error; end
|
11
|
+
class UnsupportedFeatureError < Error; end
|
12
|
+
|
13
|
+
# Initialize the TiDB extension for Ridgepole
|
14
|
+
def self.setup!
|
15
|
+
# Ensure trilogy adapter is loaded
|
16
|
+
load_trilogy_adapter
|
17
|
+
|
18
|
+
# Load the extension modules when setup is called
|
19
|
+
require_relative 'tidb/schema_dumper'
|
20
|
+
require_relative 'tidb/connection_adapters'
|
21
|
+
|
22
|
+
# Only extend if ActiveRecord is already loaded
|
23
|
+
if defined?(::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter)
|
24
|
+
::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.include(
|
25
|
+
Ridgepole::Ext::Tidb::ConnectionAdapters::TrilogyAdapter
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
return unless defined?(::ActiveRecord::SchemaDumper)
|
30
|
+
|
31
|
+
::ActiveRecord::SchemaDumper.prepend(
|
32
|
+
Ridgepole::Ext::Tidb::SchemaDumper
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_trilogy_adapter
|
37
|
+
require 'trilogy'
|
38
|
+
require 'activerecord-trilogy-adapter'
|
39
|
+
require 'active_record/connection_adapters/trilogy_adapter'
|
40
|
+
rescue LoadError => e
|
41
|
+
warn "Warning: Failed to load trilogy adapter: #{e.message}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/ridgepole/ext/tidb/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'ridgepole-ext-tidb'
|
7
|
+
spec.version = Ridgepole::Ext::Tidb::VERSION
|
8
|
+
spec.authors = ['ikad']
|
9
|
+
spec.email = ['info@forgxisto.com']
|
10
|
+
|
11
|
+
spec.summary = 'TiDB AUTO_RANDOM support extension for Ridgepole'
|
12
|
+
spec.description = "Extends Ridgepole to support TiDB's AUTO_RANDOM column attribute for seamless schema management"
|
13
|
+
spec.homepage = 'https://github.com/forgxisto/ridgepole-ext-tidb'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 3.1.0'
|
16
|
+
|
17
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/forgxisto/ridgepole-ext-tidb'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/forgxisto/ridgepole-ext-tidb/blob/main/CHANGELOG.md'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(File.expand_path(f) == __FILE__) ||
|
27
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile]) ||
|
28
|
+
f.end_with?('.gem')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = 'exe'
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ['lib']
|
34
|
+
|
35
|
+
# Dependencies
|
36
|
+
spec.add_dependency 'ridgepole'
|
37
|
+
|
38
|
+
# Development dependencies
|
39
|
+
spec.add_development_dependency 'activerecord-trilogy-adapter'
|
40
|
+
spec.add_development_dependency 'rake'
|
41
|
+
spec.add_development_dependency 'rspec'
|
42
|
+
spec.add_development_dependency 'rubocop'
|
43
|
+
spec.add_development_dependency 'trilogy'
|
44
|
+
|
45
|
+
# Ruby 3.4+ compatibility
|
46
|
+
spec.add_development_dependency 'benchmark'
|
47
|
+
spec.add_development_dependency 'bigdecimal'
|
48
|
+
spec.add_development_dependency 'logger'
|
49
|
+
spec.add_development_dependency 'mutex_m'
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ridgepole-ext-tidb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ikad
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-09-06 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ridgepole
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activerecord-trilogy-adapter
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rake
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rspec
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rubocop
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: trilogy
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: benchmark
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: bigdecimal
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: logger
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: mutex_m
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
type: :development
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
description: Extends Ridgepole to support TiDB's AUTO_RANDOM column attribute for
|
153
|
+
seamless schema management
|
154
|
+
email:
|
155
|
+
- info@forgxisto.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ".rspec"
|
161
|
+
- CHANGELOG.md
|
162
|
+
- CLAUDE.md
|
163
|
+
- Dockerfile
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- docker-compose.yml
|
168
|
+
- lib/ridgepole-ext-tidb.rb
|
169
|
+
- lib/ridgepole/ext/tidb.rb
|
170
|
+
- lib/ridgepole/ext/tidb/connection_adapters.rb
|
171
|
+
- lib/ridgepole/ext/tidb/schema_dumper.rb
|
172
|
+
- lib/ridgepole/ext/tidb/version.rb
|
173
|
+
- ridgepole-ext-tidb.gemspec
|
174
|
+
homepage: https://github.com/forgxisto/ridgepole-ext-tidb
|
175
|
+
licenses:
|
176
|
+
- MIT
|
177
|
+
metadata:
|
178
|
+
allowed_push_host: https://rubygems.org
|
179
|
+
homepage_uri: https://github.com/forgxisto/ridgepole-ext-tidb
|
180
|
+
source_code_uri: https://github.com/forgxisto/ridgepole-ext-tidb
|
181
|
+
changelog_uri: https://github.com/forgxisto/ridgepole-ext-tidb/blob/main/CHANGELOG.md
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 3.1.0
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubygems_version: 3.6.2
|
197
|
+
specification_version: 4
|
198
|
+
summary: TiDB AUTO_RANDOM support extension for Ridgepole
|
199
|
+
test_files: []
|