rubydb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  4. data/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  5. data/.github/ISSUE_TEMPLATE/performance.yml +0 -0
  6. data/.github/ISSUE_TEMPLATE/security.yml +0 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  8. data/.github/dependabot.yml +0 -0
  9. data/.github/workflows/benchmark.yml +26 -0
  10. data/.github/workflows/compatibility.yml +63 -0
  11. data/.github/workflows/fuzz.yml +33 -0
  12. data/.github/workflows/lint.yml +21 -0
  13. data/.github/workflows/operations.yml +24 -0
  14. data/.github/workflows/production-validation.yml +111 -0
  15. data/.github/workflows/release.yml +77 -0
  16. data/.github/workflows/security.yml +37 -0
  17. data/.github/workflows/test.yml +26 -0
  18. data/.github/workflows/workload.yml +58 -0
  19. data/.gitignore +43 -0
  20. data/.rspec +4 -0
  21. data/.rubocop.yml +44 -0
  22. data/.ruby-version +1 -0
  23. data/.standard.yml +14 -0
  24. data/ARCHITECTURE.md +21 -0
  25. data/CHANGELOG.md +31 -0
  26. data/CODE_OF_CONDUCT.md +13 -0
  27. data/CONTRIBUTING.md +29 -0
  28. data/GOVERNANCE.md +16 -0
  29. data/Gemfile +17 -0
  30. data/Gemfile.lock +71 -0
  31. data/LICENSE +60 -0
  32. data/README.md +162 -0
  33. data/ROADMAP.md +27 -0
  34. data/Rakefile +71 -0
  35. data/SECURITY.md +54 -0
  36. data/SUPPORT.md +14 -0
  37. data/adapters/activerecord/Gemfile +11 -0
  38. data/adapters/activerecord/README.md +9 -0
  39. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +887 -0
  40. data/adapters/activerecord/rubydb-activerecord.gemspec +27 -0
  41. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +151 -0
  42. data/adapters/ruby/README.md +18 -0
  43. data/adapters/sequel/README.md +11 -0
  44. data/bin/rubydb +19 -0
  45. data/bin/rubydb-server +126 -0
  46. data/config/development.yml +0 -0
  47. data/config/monitoring/prometheus-alerts.yml +39 -0
  48. data/config/production.yml +36 -0
  49. data/config/test.yml +0 -0
  50. data/docs/README.md +71 -0
  51. data/docs/architecture/concurrency.md +14 -0
  52. data/docs/architecture/current-state.md +125 -0
  53. data/docs/architecture/execution-engine.md +25 -0
  54. data/docs/architecture/indexes.md +19 -0
  55. data/docs/architecture/mvcc.md +19 -0
  56. data/docs/architecture/overview.md +13 -0
  57. data/docs/architecture/pages.md +11 -0
  58. data/docs/architecture/production-roadmap.md +82 -0
  59. data/docs/architecture/query-planner.md +20 -0
  60. data/docs/architecture/recovery.md +18 -0
  61. data/docs/architecture/sql-engine.md +12 -0
  62. data/docs/architecture/storage-engine.md +14 -0
  63. data/docs/architecture/transactions.md +10 -0
  64. data/docs/architecture/wal.md +28 -0
  65. data/docs/cli-cheatsheet.md +98 -0
  66. data/docs/cli.md +275 -0
  67. data/docs/contributing/architecture.md +9 -0
  68. data/docs/contributing/benchmarking.md +14 -0
  69. data/docs/contributing/development.md +16 -0
  70. data/docs/contributing/release-process.md +49 -0
  71. data/docs/contributing/testing.md +16 -0
  72. data/docs/debugging.md +229 -0
  73. data/docs/developer/branching.md +10 -0
  74. data/docs/developer/database-diff.md +10 -0
  75. data/docs/developer/local-development.md +17 -0
  76. data/docs/developer/snapshots.md +9 -0
  77. data/docs/developer/temporal-data.md +10 -0
  78. data/docs/developer-guide.md +297 -0
  79. data/docs/getting-started/first-database.md +16 -0
  80. data/docs/getting-started/first-query.md +13 -0
  81. data/docs/getting-started/installation.md +19 -0
  82. data/docs/getting-started/local-to-production.md +300 -0
  83. data/docs/getting-started/quickstart.md +17 -0
  84. data/docs/getting-started/rails.md +16 -0
  85. data/docs/hardening_backlog.md +93 -0
  86. data/docs/lessons-learned.md +112 -0
  87. data/docs/operations/backups.md +33 -0
  88. data/docs/operations/disaster-recovery.md +31 -0
  89. data/docs/operations/failover.md +30 -0
  90. data/docs/operations/monitoring.md +25 -0
  91. data/docs/operations/production-guide.md +295 -0
  92. data/docs/operations/production-runbook.md +45 -0
  93. data/docs/operations/replication.md +33 -0
  94. data/docs/operations/restore.md +6 -0
  95. data/docs/operations/runbook.md +34 -0
  96. data/docs/operations/upgrades.md +14 -0
  97. data/docs/operations/workload-testing.md +17 -0
  98. data/docs/production-readiness.md +118 -0
  99. data/docs/production_validation.md +150 -0
  100. data/docs/rails/active-record.md +11 -0
  101. data/docs/rails/compatibility-guide.md +90 -0
  102. data/docs/rails/database-yml.md +92 -0
  103. data/docs/rails/installation.md +17 -0
  104. data/docs/rails/migrations.md +17 -0
  105. data/docs/rails/production.md +82 -0
  106. data/docs/rails/troubleshooting.md +18 -0
  107. data/docs/release.md +59 -0
  108. data/docs/server/architecture.md +10 -0
  109. data/docs/server/authentication.md +10 -0
  110. data/docs/server/configuration.md +16 -0
  111. data/docs/server/connection-pooling.md +10 -0
  112. data/docs/server/deployment.md +10 -0
  113. data/docs/server/protocol.md +12 -0
  114. data/docs/sql/compatibility-guide.md +82 -0
  115. data/docs/sql/compatibility.md +39 -0
  116. data/docs/sql/data-types.md +10 -0
  117. data/docs/sql/functions.md +9 -0
  118. data/docs/sql/joins.md +9 -0
  119. data/docs/sql/operators.md +9 -0
  120. data/docs/sql/sqlite-compatibility.md +21 -0
  121. data/docs/sql/syntax.md +10 -0
  122. data/docs/sql/transactions.md +10 -0
  123. data/docs/troubleshooting.md +244 -0
  124. data/exe/rubydb +23 -0
  125. data/lib/rubydb/backup/archive.rb +334 -0
  126. data/lib/rubydb/backup/backup.rb +401 -0
  127. data/lib/rubydb/backup/incremental.rb +353 -0
  128. data/lib/rubydb/backup/restore.rb +290 -0
  129. data/lib/rubydb/backup/snapshot.rb +267 -0
  130. data/lib/rubydb/backup/verification.rb +279 -0
  131. data/lib/rubydb/branching/branch.rb +181 -0
  132. data/lib/rubydb/branching/branch_manager.rb +311 -0
  133. data/lib/rubydb/branching/branch_metadata.rb +140 -0
  134. data/lib/rubydb/branching/checkout.rb +166 -0
  135. data/lib/rubydb/branching/copy_on_write.rb +272 -0
  136. data/lib/rubydb/branching/diff.rb +138 -0
  137. data/lib/rubydb/branching/merge.rb +285 -0
  138. data/lib/rubydb/build_info.rb +15 -0
  139. data/lib/rubydb/catalog/catalog.rb +391 -0
  140. data/lib/rubydb/catalog/column.rb +112 -0
  141. data/lib/rubydb/catalog/constraint.rb +180 -0
  142. data/lib/rubydb/catalog/database.rb +184 -0
  143. data/lib/rubydb/catalog/index.rb +97 -0
  144. data/lib/rubydb/catalog/schema.rb +103 -0
  145. data/lib/rubydb/catalog/sequence.rb +90 -0
  146. data/lib/rubydb/catalog/system_catalog.rb +698 -0
  147. data/lib/rubydb/catalog/table.rb +178 -0
  148. data/lib/rubydb/catalog/trigger.rb +102 -0
  149. data/lib/rubydb/catalog/view.rb +66 -0
  150. data/lib/rubydb/cli/application.rb +163 -0
  151. data/lib/rubydb/cli/commands/backup.rb +81 -0
  152. data/lib/rubydb/cli/commands/branch.rb +72 -0
  153. data/lib/rubydb/cli/commands/checkout.rb +54 -0
  154. data/lib/rubydb/cli/commands/create.rb +58 -0
  155. data/lib/rubydb/cli/commands/diff.rb +77 -0
  156. data/lib/rubydb/cli/commands/doctor.rb +74 -0
  157. data/lib/rubydb/cli/commands/drop.rb +57 -0
  158. data/lib/rubydb/cli/commands/init.rb +102 -0
  159. data/lib/rubydb/cli/commands/inspect.rb +95 -0
  160. data/lib/rubydb/cli/commands/merge.rb +63 -0
  161. data/lib/rubydb/cli/commands/migrate.rb +62 -0
  162. data/lib/rubydb/cli/commands/restart.rb +39 -0
  163. data/lib/rubydb/cli/commands/restore.rb +121 -0
  164. data/lib/rubydb/cli/commands/shell.rb +365 -0
  165. data/lib/rubydb/cli/commands/snapshot.rb +79 -0
  166. data/lib/rubydb/cli/commands/start.rb +82 -0
  167. data/lib/rubydb/cli/commands/status.rb +92 -0
  168. data/lib/rubydb/cli/commands/stop.rb +47 -0
  169. data/lib/rubydb/cli/commands/vacuum.rb +58 -0
  170. data/lib/rubydb/cli/formatter.rb +221 -0
  171. data/lib/rubydb/cli/output.rb +168 -0
  172. data/lib/rubydb/client/client.rb +304 -0
  173. data/lib/rubydb/client/connection.rb +415 -0
  174. data/lib/rubydb/client/connection_pool.rb +168 -0
  175. data/lib/rubydb/client/connection_url.rb +96 -0
  176. data/lib/rubydb/client/prepared_statement.rb +60 -0
  177. data/lib/rubydb/client/result.rb +123 -0
  178. data/lib/rubydb/client/statement.rb +52 -0
  179. data/lib/rubydb/client/transaction.rb +130 -0
  180. data/lib/rubydb/concurrency/concurrency.rb +19 -0
  181. data/lib/rubydb/concurrency/deadlock_detector.rb +150 -0
  182. data/lib/rubydb/concurrency/latch.rb +101 -0
  183. data/lib/rubydb/concurrency/lock_graph.rb +165 -0
  184. data/lib/rubydb/concurrency/mutex.rb +183 -0
  185. data/lib/rubydb/concurrency/rw_lock.rb +180 -0
  186. data/lib/rubydb/concurrency/scheduler.rb +250 -0
  187. data/lib/rubydb/concurrency/worker_pool.rb +143 -0
  188. data/lib/rubydb/configuration/config.rb +170 -0
  189. data/lib/rubydb/configuration/defaults.rb +179 -0
  190. data/lib/rubydb/configuration/environment.rb +152 -0
  191. data/lib/rubydb/configuration/parser.rb +185 -0
  192. data/lib/rubydb/configuration/validation.rb +221 -0
  193. data/lib/rubydb/constants.rb +74 -0
  194. data/lib/rubydb/constraints/check.rb +181 -0
  195. data/lib/rubydb/constraints/constraint.rb +101 -0
  196. data/lib/rubydb/constraints/foreign_key.rb +130 -0
  197. data/lib/rubydb/constraints/not_null.rb +64 -0
  198. data/lib/rubydb/constraints/primary_key.rb +99 -0
  199. data/lib/rubydb/constraints/unique.rb +108 -0
  200. data/lib/rubydb/constraints/validator.rb +350 -0
  201. data/lib/rubydb/errors/authentication_error.rb +10 -0
  202. data/lib/rubydb/errors/authorization_error.rb +23 -0
  203. data/lib/rubydb/errors/client_error.rb +10 -0
  204. data/lib/rubydb/errors/configuration_error.rb +10 -0
  205. data/lib/rubydb/errors/connection_error.rb +10 -0
  206. data/lib/rubydb/errors/constraint_error.rb +23 -0
  207. data/lib/rubydb/errors/corruption_error.rb +10 -0
  208. data/lib/rubydb/errors/database_error.rb +10 -0
  209. data/lib/rubydb/errors/error.rb +20 -0
  210. data/lib/rubydb/errors/execution_error.rb +10 -0
  211. data/lib/rubydb/errors/parser_error.rb +10 -0
  212. data/lib/rubydb/errors/recovery_error.rb +10 -0
  213. data/lib/rubydb/errors/replication_error.rb +10 -0
  214. data/lib/rubydb/errors/server_error.rb +6 -0
  215. data/lib/rubydb/errors/storage_error.rb +10 -0
  216. data/lib/rubydb/errors/transaction_error.rb +10 -0
  217. data/lib/rubydb/execution/aggregate_executor.rb +138 -0
  218. data/lib/rubydb/execution/delete_executor.rb +112 -0
  219. data/lib/rubydb/execution/distinct_executor.rb +135 -0
  220. data/lib/rubydb/execution/executor.rb +1192 -0
  221. data/lib/rubydb/execution/expression.rb +193 -0
  222. data/lib/rubydb/execution/index_scan.rb +142 -0
  223. data/lib/rubydb/execution/insert_executor.rb +217 -0
  224. data/lib/rubydb/execution/join_executor.rb +249 -0
  225. data/lib/rubydb/execution/limit_executor.rb +85 -0
  226. data/lib/rubydb/execution/optimizer.rb +215 -0
  227. data/lib/rubydb/execution/plan.rb +361 -0
  228. data/lib/rubydb/execution/planner.rb +546 -0
  229. data/lib/rubydb/execution/predicate.rb +235 -0
  230. data/lib/rubydb/execution/scan.rb +49 -0
  231. data/lib/rubydb/execution/sequential_scan.rb +63 -0
  232. data/lib/rubydb/execution/sort_executor.rb +185 -0
  233. data/lib/rubydb/execution/update_executor.rb +162 -0
  234. data/lib/rubydb/functions/aggregate.rb +70 -0
  235. data/lib/rubydb/functions/date_functions.rb +278 -0
  236. data/lib/rubydb/functions/function.rb +85 -0
  237. data/lib/rubydb/functions/json_functions.rb +215 -0
  238. data/lib/rubydb/functions/numeric_functions.rb +346 -0
  239. data/lib/rubydb/functions/scalar.rb +52 -0
  240. data/lib/rubydb/functions/string_functions.rb +383 -0
  241. data/lib/rubydb/functions/system_functions.rb +246 -0
  242. data/lib/rubydb/history/as_of.rb +238 -0
  243. data/lib/rubydb/history/change.rb +105 -0
  244. data/lib/rubydb/history/history.rb +131 -0
  245. data/lib/rubydb/history/history_manager.rb +229 -0
  246. data/lib/rubydb/history/temporal_query.rb +202 -0
  247. data/lib/rubydb/history/timeline.rb +144 -0
  248. data/lib/rubydb/indexes/btree.rb +186 -0
  249. data/lib/rubydb/indexes/btree_cursor.rb +258 -0
  250. data/lib/rubydb/indexes/btree_node.rb +385 -0
  251. data/lib/rubydb/indexes/hash_index.rb +150 -0
  252. data/lib/rubydb/indexes/index.rb +71 -0
  253. data/lib/rubydb/indexes/index_manager.rb +406 -0
  254. data/lib/rubydb/indexes/index_scan.rb +470 -0
  255. data/lib/rubydb/migrations/migration.rb +254 -0
  256. data/lib/rubydb/migrations/migration_lock.rb +146 -0
  257. data/lib/rubydb/migrations/migration_manager.rb +176 -0
  258. data/lib/rubydb/migrations/migration_version.rb +71 -0
  259. data/lib/rubydb/migrations/schema_diff.rb +211 -0
  260. data/lib/rubydb/migrations/schema_version.rb +64 -0
  261. data/lib/rubydb/monitoring/events.rb +160 -0
  262. data/lib/rubydb/monitoring/health.rb +222 -0
  263. data/lib/rubydb/monitoring/logger.rb +193 -0
  264. data/lib/rubydb/monitoring/metrics.rb +359 -0
  265. data/lib/rubydb/monitoring/performance.rb +176 -0
  266. data/lib/rubydb/monitoring/statistics.rb +170 -0
  267. data/lib/rubydb/mvcc/garbage_collector.rb +199 -0
  268. data/lib/rubydb/mvcc/mvcc.rb +16 -0
  269. data/lib/rubydb/mvcc/snapshot.rb +147 -0
  270. data/lib/rubydb/mvcc/vacuum.rb +180 -0
  271. data/lib/rubydb/mvcc/version.rb +106 -0
  272. data/lib/rubydb/mvcc/version_store.rb +398 -0
  273. data/lib/rubydb/mvcc/visibility.rb +109 -0
  274. data/lib/rubydb/protocol/capabilities.rb +125 -0
  275. data/lib/rubydb/protocol/decoder.rb +145 -0
  276. data/lib/rubydb/protocol/encoder.rb +136 -0
  277. data/lib/rubydb/protocol/handshake.rb +305 -0
  278. data/lib/rubydb/protocol/message.rb +121 -0
  279. data/lib/rubydb/protocol/protocol.rb +277 -0
  280. data/lib/rubydb/protocol/version.rb +54 -0
  281. data/lib/rubydb/rails/adapter.rb +239 -0
  282. data/lib/rubydb/rails/connection.rb +314 -0
  283. data/lib/rubydb/rails/database_statements.rb +122 -0
  284. data/lib/rubydb/rails/migration.rb +131 -0
  285. data/lib/rubydb/rails/quoting.rb +109 -0
  286. data/lib/rubydb/rails/result.rb +120 -0
  287. data/lib/rubydb/rails/schema_statements.rb +339 -0
  288. data/lib/rubydb/rails/transaction.rb +105 -0
  289. data/lib/rubydb/rails/type.rb +126 -0
  290. data/lib/rubydb/recovery/checkpoint.rb +257 -0
  291. data/lib/rubydb/recovery/consistency.rb +467 -0
  292. data/lib/rubydb/recovery/corruption_detector.rb +5 -0
  293. data/lib/rubydb/recovery/crash_recovery.rb +387 -0
  294. data/lib/rubydb/recovery/recovery_manager.rb +206 -0
  295. data/lib/rubydb/recovery/redo.rb +237 -0
  296. data/lib/rubydb/recovery/undo.rb +206 -0
  297. data/lib/rubydb/replication/failover.rb +5 -0
  298. data/lib/rubydb/replication/fencing.rb +63 -0
  299. data/lib/rubydb/replication/primary.rb +450 -0
  300. data/lib/rubydb/replication/replica.rb +384 -0
  301. data/lib/rubydb/replication/replication_log.rb +200 -0
  302. data/lib/rubydb/replication/replication_manager.rb +308 -0
  303. data/lib/rubydb/replication/replication_slot.rb +295 -0
  304. data/lib/rubydb/replication/replication_stream.rb +201 -0
  305. data/lib/rubydb/rubydb.rb +560 -0
  306. data/lib/rubydb/security/access_control.rb +254 -0
  307. data/lib/rubydb/security/audit_log.rb +213 -0
  308. data/lib/rubydb/security/authentication.rb +302 -0
  309. data/lib/rubydb/security/authorization.rb +282 -0
  310. data/lib/rubydb/security/credentials.rb +196 -0
  311. data/lib/rubydb/security/password.rb +215 -0
  312. data/lib/rubydb/security/permissions.rb +74 -0
  313. data/lib/rubydb/security/role.rb +101 -0
  314. data/lib/rubydb/security/user.rb +86 -0
  315. data/lib/rubydb/server/connection.rb +366 -0
  316. data/lib/rubydb/server/connection_pool.rb +193 -0
  317. data/lib/rubydb/server/lifecycle.rb +228 -0
  318. data/lib/rubydb/server/listener.rb +136 -0
  319. data/lib/rubydb/server/request_handler.rb +276 -0
  320. data/lib/rubydb/server/server.rb +364 -0
  321. data/lib/rubydb/server/session.rb +369 -0
  322. data/lib/rubydb/server/worker.rb +210 -0
  323. data/lib/rubydb/server/worker_pool.rb +168 -0
  324. data/lib/rubydb/sql/ast/alter_table.rb +169 -0
  325. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -0
  326. data/lib/rubydb/sql/ast/commit.rb +37 -0
  327. data/lib/rubydb/sql/ast/constraint.rb +83 -0
  328. data/lib/rubydb/sql/ast/create_database.rb +41 -0
  329. data/lib/rubydb/sql/ast/create_index.rb +61 -0
  330. data/lib/rubydb/sql/ast/create_schema.rb +52 -0
  331. data/lib/rubydb/sql/ast/create_table.rb +187 -0
  332. data/lib/rubydb/sql/ast/delete.rb +54 -0
  333. data/lib/rubydb/sql/ast/drop_database.rb +41 -0
  334. data/lib/rubydb/sql/ast/drop_index.rb +41 -0
  335. data/lib/rubydb/sql/ast/drop_schema.rb +49 -0
  336. data/lib/rubydb/sql/ast/drop_table.rb +49 -0
  337. data/lib/rubydb/sql/ast/explain.rb +64 -0
  338. data/lib/rubydb/sql/ast/expression.rb +604 -0
  339. data/lib/rubydb/sql/ast/insert.rb +82 -0
  340. data/lib/rubydb/sql/ast/node.rb +42 -0
  341. data/lib/rubydb/sql/ast/rollback.rb +63 -0
  342. data/lib/rubydb/sql/ast/savepoint.rb +59 -0
  343. data/lib/rubydb/sql/ast/select.rb +88 -0
  344. data/lib/rubydb/sql/ast/set_operation.rb +20 -0
  345. data/lib/rubydb/sql/ast/trigger.rb +29 -0
  346. data/lib/rubydb/sql/ast/update.rb +88 -0
  347. data/lib/rubydb/sql/ast/vacuum.rb +19 -0
  348. data/lib/rubydb/sql/ast/view.rb +32 -0
  349. data/lib/rubydb/sql/ast/with.rb +32 -0
  350. data/lib/rubydb/sql/grammar.rb +86 -0
  351. data/lib/rubydb/sql/keywords.rb +156 -0
  352. data/lib/rubydb/sql/lexer.rb +214 -0
  353. data/lib/rubydb/sql/operators.rb +100 -0
  354. data/lib/rubydb/sql/parser.rb +1185 -0
  355. data/lib/rubydb/sql/planner/analyzer.rb +302 -0
  356. data/lib/rubydb/sql/planner/binder.rb +550 -0
  357. data/lib/rubydb/sql/planner/type_checker.rb +431 -0
  358. data/lib/rubydb/sql/token.rb +210 -0
  359. data/lib/rubydb/storage/buffer_frame.rb +44 -0
  360. data/lib/rubydb/storage/buffer_pool.rb +155 -0
  361. data/lib/rubydb/storage/database_lock.rb +74 -0
  362. data/lib/rubydb/storage/deserializer.rb +342 -0
  363. data/lib/rubydb/storage/engine.rb +2338 -0
  364. data/lib/rubydb/storage/file_manager.rb +187 -0
  365. data/lib/rubydb/storage/free_space_map.rb +81 -0
  366. data/lib/rubydb/storage/page.rb +94 -0
  367. data/lib/rubydb/storage/page_allocator.rb +855 -0
  368. data/lib/rubydb/storage/page_header.rb +67 -0
  369. data/lib/rubydb/storage/page_manager.rb +131 -0
  370. data/lib/rubydb/storage/record.rb +58 -0
  371. data/lib/rubydb/storage/row.rb +78 -0
  372. data/lib/rubydb/storage/serializer.rb +51 -0
  373. data/lib/rubydb/storage/storage_layout.rb +151 -0
  374. data/lib/rubydb/storage/storage_manager.rb +114 -0
  375. data/lib/rubydb/storage/tuple.rb +461 -0
  376. data/lib/rubydb/storage/visibility_map.rb +984 -0
  377. data/lib/rubydb/transactions/commit_manager.rb +220 -0
  378. data/lib/rubydb/transactions/isolation.rb +98 -0
  379. data/lib/rubydb/transactions/lock.rb +76 -0
  380. data/lib/rubydb/transactions/lock_manager.rb +362 -0
  381. data/lib/rubydb/transactions/savepoint.rb +143 -0
  382. data/lib/rubydb/transactions/transaction.rb +215 -0
  383. data/lib/rubydb/transactions/transaction_id.rb +84 -0
  384. data/lib/rubydb/transactions/transaction_log.rb +257 -0
  385. data/lib/rubydb/transactions/transaction_manager.rb +435 -0
  386. data/lib/rubydb/types/bigint.rb +36 -0
  387. data/lib/rubydb/types/blob.rb +37 -0
  388. data/lib/rubydb/types/boolean.rb +34 -0
  389. data/lib/rubydb/types/date.rb +39 -0
  390. data/lib/rubydb/types/decimal.rb +48 -0
  391. data/lib/rubydb/types/float.rb +34 -0
  392. data/lib/rubydb/types/integer.rb +36 -0
  393. data/lib/rubydb/types/json.rb +41 -0
  394. data/lib/rubydb/types/null.rb +34 -0
  395. data/lib/rubydb/types/smallint.rb +36 -0
  396. data/lib/rubydb/types/text.rb +37 -0
  397. data/lib/rubydb/types/time.rb +46 -0
  398. data/lib/rubydb/types/timestamp.rb +39 -0
  399. data/lib/rubydb/types/type.rb +119 -0
  400. data/lib/rubydb/types/uuid.rb +47 -0
  401. data/lib/rubydb/types/varchar.rb +37 -0
  402. data/lib/rubydb/version.rb +33 -0
  403. data/lib/rubydb/wal/archive.rb +193 -0
  404. data/lib/rubydb/wal/checkpoint.rb +183 -0
  405. data/lib/rubydb/wal/lsn.rb +94 -0
  406. data/lib/rubydb/wal/reader.rb +260 -0
  407. data/lib/rubydb/wal/record.rb +105 -0
  408. data/lib/rubydb/wal/segment.rb +193 -0
  409. data/lib/rubydb/wal/wal.rb +452 -0
  410. data/lib/rubydb/wal/writer.rb +236 -0
  411. data/lib/rubydb.rb +7 -0
  412. data/packaging/docker/Dockerfile +36 -0
  413. data/packaging/docker/docker-compose.failover.yml +43 -0
  414. data/packaging/docker/entrypoint.sh +15 -0
  415. data/packaging/homebrew/rubydb.rb +19 -0
  416. data/packaging/systemd/rubydb.service +22 -0
  417. data/rubydb.gemspec +57 -0
  418. data/scripts/benchmark +7 -0
  419. data/scripts/durability_drill +37 -0
  420. data/scripts/format +0 -0
  421. data/scripts/fuzz +63 -0
  422. data/scripts/lint +0 -0
  423. data/scripts/release +50 -0
  424. data/scripts/release_check +43 -0
  425. data/scripts/replication_failover_drill +250 -0
  426. data/scripts/replication_network_failover_drill +255 -0
  427. data/scripts/restore_drill +45 -0
  428. data/scripts/security +0 -0
  429. data/scripts/setup +0 -0
  430. data/scripts/test +0 -0
  431. data/scripts/test-chaos +0 -0
  432. data/scripts/test-crash +0 -0
  433. data/scripts/test-integration +0 -0
  434. data/scripts/test-unit +0 -0
  435. data/visibility_map.json +1 -0
  436. metadata +538 -0
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module RubyDB
6
+ module Branching
7
+ # Merge - Handles branch merging
8
+ class Merge
9
+ attr_reader :stats
10
+
11
+ # Merge strategies
12
+ STRATEGY_FAST_FORWARD = :fast_forward
13
+ STRATEGY_RECURSIVE = :recursive
14
+ STRATEGY_OCTOPUS = :octopus
15
+ STRATEGY_SQUASH = :squash
16
+
17
+ def initialize(engine, branch_manager, config = {})
18
+ @engine = engine
19
+ @branch_manager = branch_manager
20
+ @config = config
21
+ @strategy = config[:strategy] || STRATEGY_FAST_FORWARD
22
+ @merge_history = []
23
+ @last_merge = nil
24
+ @stats = {
25
+ merges: 0,
26
+ successful_merges: 0,
27
+ failed_merges: 0,
28
+ conflicts_detected: 0,
29
+ conflicts_resolved: 0,
30
+ merge_time_ms: 0,
31
+ avg_merge_time_ms: 0
32
+ }
33
+ @lock = Mutex.new
34
+ @max_history = config[:max_history] || 100
35
+ end
36
+
37
+ def merge(source_branch, target_branch = nil, options = {})
38
+ @lock.synchronize do
39
+ start_time = Time.now
40
+ @stats[:merges] += 1
41
+
42
+ target_branch ||= if @branch_manager.respond_to?(:current_branch_name)
43
+ @branch_manager.current_branch_name
44
+ else
45
+ @branch_manager.current_branch&.name
46
+ end
47
+ unless target_branch
48
+ @stats[:failed_merges] += 1
49
+ return { success: false, error: "No target branch specified" }
50
+ end
51
+
52
+ source = @branch_manager.get_branch(source_branch)
53
+ target = @branch_manager.get_branch(target_branch)
54
+
55
+ unless source
56
+ @stats[:failed_merges] += 1
57
+ return { success: false, error: "Source branch '#{source_branch}' not found" }
58
+ end
59
+
60
+ unless target
61
+ @stats[:failed_merges] += 1
62
+ return { success: false, error: "Target branch '#{target_branch}' not found" }
63
+ end
64
+
65
+ if source == target
66
+ @stats[:failed_merges] += 1
67
+ return { success: false, error: "Cannot merge a branch with itself" }
68
+ end
69
+
70
+ begin
71
+ # Detect conflicts
72
+ conflicts = detect_conflicts(source, target)
73
+
74
+ if conflicts.any?
75
+ @stats[:conflicts_detected] += 1
76
+
77
+ if options[:abort_on_conflict]
78
+ @stats[:failed_merges] += 1
79
+ return {
80
+ success: false,
81
+ conflicts: conflicts,
82
+ message: "Conflicts detected, merge aborted"
83
+ }
84
+ end
85
+
86
+ # Try to resolve conflicts
87
+ if options[:resolve_conflicts]
88
+ resolved = resolve_conflicts(conflicts, options)
89
+ conflicts = resolved[:remaining] if resolved[:remaining]
90
+ end
91
+ if conflicts.any?
92
+ @stats[:failed_merges] += 1
93
+ return { success: false, conflicts: conflicts, message: "Unresolved conflicts" }
94
+ end
95
+ end
96
+
97
+ # Perform merge based on strategy
98
+ result = case @strategy
99
+ when STRATEGY_FAST_FORWARD
100
+ merge_fast_forward(source, target, options)
101
+ when STRATEGY_RECURSIVE
102
+ merge_recursive(source, target, options)
103
+ when STRATEGY_OCTOPUS
104
+ merge_octopus(source, target, options)
105
+ when STRATEGY_SQUASH
106
+ merge_squash(source, target, options)
107
+ else
108
+ merge_fast_forward(source, target, options)
109
+ end
110
+
111
+ if result[:success]
112
+ previous_changes = target.logical_changes
113
+ merge_changes = Array(result[:changes])
114
+ merged_changes = previous_changes + merge_changes
115
+ if @engine && @branch_manager.respond_to?(:current_branch_name) &&
116
+ @branch_manager.current_branch_name == target_branch &&
117
+ @engine.respond_to?(:apply_branch_state)
118
+ begin
119
+ @engine.apply_branch_state(base: target.state_snapshot, changes: merged_changes)
120
+ rescue => error
121
+ @stats[:failed_merges] += 1
122
+ return { success: false, error: "Unable to apply merged state: #{error.message}" }
123
+ end
124
+ end
125
+
126
+ # Update branch manager
127
+ target.merge_changes(merge_changes)
128
+ @branch_manager.persist! if @branch_manager.respond_to?(:persist!)
129
+ @last_merge = {
130
+ source: source_branch,
131
+ target: target_branch,
132
+ previous_changes: previous_changes,
133
+ merged_count: merge_changes.size
134
+ }
135
+
136
+ @stats[:successful_merges] += 1
137
+
138
+ elapsed_ms = (Time.now - start_time) * 1000
139
+ @stats[:merge_time_ms] += elapsed_ms
140
+ @stats[:avg_merge_time_ms] = @stats[:merge_time_ms] / @stats[:successful_merges]
141
+
142
+ # Record history
143
+ record_merge(source_branch, target_branch, options)
144
+
145
+ result.merge(elapsed_ms: elapsed_ms)
146
+ else
147
+ @stats[:failed_merges] += 1
148
+ result
149
+ end
150
+
151
+ rescue => e
152
+ @stats[:failed_merges] += 1
153
+ { success: false, error: e.message }
154
+ end
155
+ end
156
+ end
157
+
158
+ def merge_abort
159
+ @lock.synchronize do
160
+ merge = @last_merge
161
+ return { success: false, error: "No merge to abort" } unless merge
162
+
163
+ target = @branch_manager.get_branch(merge[:target])
164
+ unless target
165
+ return { success: false, error: "Target branch '#{merge[:target]}' not found" }
166
+ end
167
+
168
+ target.rollback(merge[:merged_count]) if merge[:merged_count].positive?
169
+ if @engine && @branch_manager.respond_to?(:current_branch_name) &&
170
+ @branch_manager.current_branch_name == merge[:target] &&
171
+ @engine.respond_to?(:apply_branch_state)
172
+ @engine.apply_branch_state(base: target.state_snapshot, changes: merge[:previous_changes])
173
+ end
174
+ @branch_manager.persist! if @branch_manager.respond_to?(:persist!)
175
+ @merge_history.pop
176
+ @last_merge = nil
177
+
178
+ { success: true, target: merge[:target], reverted_changes: merge[:merged_count] }
179
+ end
180
+ end
181
+
182
+ def merge_history
183
+ @merge_history.dup
184
+ end
185
+
186
+ def stats
187
+ @lock.synchronize do
188
+ @stats.merge({
189
+ strategy: @strategy,
190
+ history_size: @merge_history.size,
191
+ max_history: @max_history
192
+ })
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def detect_conflicts(source, target)
199
+ target_changes = target.respond_to?(:logical_changes) ? target.logical_changes : target.changes
200
+ source_changes = source.respond_to?(:logical_changes) ? source.logical_changes : source.changes
201
+ target_keys = target_changes.map { |change| change_key(change) }
202
+ source_changes.filter_map do |change|
203
+ next unless target_keys.include?(change_key(change))
204
+ other = target_changes.find { |candidate| change_key(candidate) == change_key(change) }
205
+ { key: change_key(change), source: change, target: other } unless other == change
206
+ end
207
+ end
208
+
209
+ def resolve_conflicts(conflicts, options)
210
+ resolver = options[:conflict_resolver]
211
+ raise ArgumentError, "conflict_resolver is required" unless resolver.respond_to?(:call)
212
+ remaining = conflicts.filter_map { |conflict| resolver.call(conflict) ? nil : conflict }
213
+ { remaining: remaining }
214
+ end
215
+
216
+ def change_key(change)
217
+ [change[:table] || change["table"], change[:row_id] || change["row_id"], change[:column] || change["column"]]
218
+ end
219
+
220
+ def merge_fast_forward(source, target, options)
221
+ # Fast-forward merge
222
+ changes = source.respond_to?(:logical_changes) ? source.logical_changes : source.changes
223
+
224
+ {
225
+ success: true,
226
+ changes: changes,
227
+ strategy: :fast_forward,
228
+ message: "Fast-forward merge completed"
229
+ }
230
+ end
231
+
232
+ def merge_recursive(source, target, options)
233
+ # Recursive merge
234
+ changes = source.respond_to?(:logical_changes) ? source.logical_changes : source.changes
235
+
236
+ {
237
+ success: true,
238
+ changes: changes,
239
+ strategy: :recursive,
240
+ message: "Recursive merge completed"
241
+ }
242
+ end
243
+
244
+ def merge_octopus(source, target, options)
245
+ # Octopus merge (for merging multiple branches)
246
+ changes = source.respond_to?(:logical_changes) ? source.logical_changes : source.changes
247
+
248
+ {
249
+ success: true,
250
+ changes: changes,
251
+ strategy: :octopus,
252
+ message: "Octopus merge completed"
253
+ }
254
+ end
255
+
256
+ def merge_squash(source, target, options)
257
+ # Squash merge (combine all changes into one commit)
258
+ changes = source.respond_to?(:logical_changes) ? source.logical_changes : source.changes
259
+
260
+ {
261
+ success: true,
262
+ changes: changes,
263
+ strategy: :squash,
264
+ message: "Squash merge completed"
265
+ }
266
+ end
267
+
268
+ def record_merge(source_branch, target_branch, options)
269
+ entry = {
270
+ source: source_branch,
271
+ target: target_branch,
272
+ timestamp: Time.now.iso8601,
273
+ options: options,
274
+ strategy: @strategy
275
+ }
276
+
277
+ @merge_history << entry
278
+
279
+ if @merge_history.size > @max_history
280
+ @merge_history.shift
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyDB
4
+ module BuildInfo
5
+ VERSION = RubyDB::VERSION if defined?(RubyDB::VERSION)
6
+
7
+ def self.to_h
8
+ {
9
+ version: (defined?(RubyDB::VERSION) ? RubyDB::VERSION : "unknown"),
10
+ ruby: RUBY_VERSION,
11
+ platform: RUBY_PLATFORM
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,391 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module RubyDB
6
+ module Catalog
7
+ # Main catalog - manages all database metadata
8
+ class Catalog
9
+ attr_reader :databases, :current_database, :tables, :system_catalog
10
+
11
+ def initialize
12
+ @databases = {}
13
+ @current_database = nil
14
+ @tables = {}
15
+ @sequences = {}
16
+ @views = {}
17
+ @triggers = {}
18
+ @system_catalog = SystemCatalog.new(self)
19
+ @version = 1
20
+ @created_at = Time.now
21
+ @modified_at = Time.now
22
+ end
23
+
24
+ # --- Database Management ---
25
+
26
+ def create_database(name, if_not_exists: false)
27
+ if @databases.key?(name)
28
+ return if if_not_exists
29
+ raise DatabaseError, "Database '#{name}' already exists"
30
+ end
31
+
32
+ db = Database.new(name)
33
+ @databases[name] = db
34
+ @current_database = db if @databases.size == 1
35
+ @modified_at = Time.now
36
+ db
37
+ end
38
+
39
+ def drop_database(name, if_exists: false)
40
+ unless @databases.key?(name)
41
+ return if if_exists
42
+ raise DatabaseError, "Database '#{name}' does not exist"
43
+ end
44
+
45
+ @databases.delete(name)
46
+ @current_database = @databases.values.first if @current_database&.name == name
47
+ @modified_at = Time.now
48
+ true
49
+ end
50
+
51
+ def find_database(name)
52
+ @databases[name]
53
+ end
54
+
55
+ def create_schema(name, if_not_exists: false, authorization: nil)
56
+ raise DatabaseError, "No database selected" unless @current_database
57
+ schema = @current_database.create_schema(name, if_not_exists: if_not_exists, authorization: authorization)
58
+ @modified_at = Time.now
59
+ schema
60
+ end
61
+
62
+ def drop_schema(name, if_exists: false, cascade: false)
63
+ raise DatabaseError, "No database selected" unless @current_database
64
+ result = @current_database.drop_schema(name, if_exists: if_exists, cascade: cascade)
65
+ @modified_at = Time.now
66
+ result
67
+ end
68
+
69
+ def find_schema(name)
70
+ @current_database&.find_schema(name)
71
+ end
72
+
73
+ def use_database(name)
74
+ db = @databases[name]
75
+ raise DatabaseError, "Database '#{name}' does not exist" unless db
76
+ @current_database = db
77
+ @modified_at = Time.now
78
+ db
79
+ end
80
+
81
+ def current_database_name
82
+ @current_database&.name
83
+ end
84
+
85
+ # --- Table Management ---
86
+
87
+ def create_table(name, &block)
88
+ raise DatabaseError, "No database selected" unless @current_database
89
+
90
+ if @current_database.table_exists?(name)
91
+ raise DatabaseError, "Table '#{name}' already exists in database '#{@current_database.name}'"
92
+ end
93
+
94
+ table = Table.new(name, @current_database)
95
+ @current_database.add_table(table)
96
+
97
+ # Execute block to define columns
98
+ if block_given?
99
+ builder = TableBuilder.new(table)
100
+ yield builder
101
+ end
102
+
103
+ @tables[name] = table
104
+ @modified_at = Time.now
105
+ table
106
+ end
107
+
108
+ def drop_table(name, if_exists: false)
109
+ raise DatabaseError, "No database selected" unless @current_database
110
+
111
+ unless @current_database.table_exists?(name)
112
+ return if if_exists
113
+ raise DatabaseError, "Table '#{name}' does not exist"
114
+ end
115
+
116
+ table = @current_database.drop_table(name)
117
+ @tables.delete(name)
118
+ @modified_at = Time.now
119
+ table
120
+ end
121
+
122
+ def find_table(name)
123
+ return nil unless @current_database
124
+ @current_database.find_table(name)
125
+ end
126
+
127
+ def tables
128
+ return [] unless @current_database
129
+ @current_database.tables.values
130
+ end
131
+
132
+ def table_exists?(name)
133
+ return false unless @current_database
134
+ @current_database.table_exists?(name)
135
+ end
136
+
137
+ # --- Sequence Management ---
138
+
139
+ def create_sequence(name, options = {})
140
+ raise DatabaseError, "No database selected" unless @current_database
141
+
142
+ if @current_database.sequence_exists?(name)
143
+ raise DatabaseError, "Sequence '#{name}' already exists"
144
+ end
145
+
146
+ sequence = Sequence.new(name, options)
147
+ @current_database.add_sequence(sequence)
148
+ @sequences[name] = sequence
149
+ @modified_at = Time.now
150
+ sequence
151
+ end
152
+
153
+ def drop_sequence(name, if_exists: false)
154
+ raise DatabaseError, "No database selected" unless @current_database
155
+
156
+ unless @current_database.sequence_exists?(name)
157
+ return if if_exists
158
+ raise DatabaseError, "Sequence '#{name}' does not exist"
159
+ end
160
+
161
+ sequence = @current_database.drop_sequence(name)
162
+ @sequences.delete(name)
163
+ @modified_at = Time.now
164
+ sequence
165
+ end
166
+
167
+ def find_sequence(name)
168
+ return nil unless @current_database
169
+ @current_database.find_sequence(name)
170
+ end
171
+
172
+ def next_value_for_sequence(name)
173
+ seq = find_sequence(name)
174
+ raise DatabaseError, "Sequence '#{name}' does not exist" unless seq
175
+ seq.next_value
176
+ end
177
+
178
+ # --- View Management ---
179
+
180
+ def create_view(name, query, if_not_exists: false)
181
+ raise DatabaseError, "No database selected" unless @current_database
182
+
183
+ if @current_database.view_exists?(name)
184
+ return @current_database.find_view(name) if if_not_exists
185
+ raise DatabaseError, "View '#{name}' already exists"
186
+ end
187
+
188
+ view = View.new(name, query)
189
+ @current_database.add_view(view)
190
+ @views[name] = view
191
+ @modified_at = Time.now
192
+ view
193
+ end
194
+
195
+ def drop_view(name, if_exists: false)
196
+ raise DatabaseError, "No database selected" unless @current_database
197
+
198
+ unless @current_database.view_exists?(name)
199
+ return if if_exists
200
+ raise DatabaseError, "View '#{name}' does not exist"
201
+ end
202
+
203
+ view = @current_database.drop_view(name)
204
+ @views.delete(name)
205
+ @modified_at = Time.now
206
+ view
207
+ end
208
+
209
+ def find_view(name)
210
+ return nil unless @current_database
211
+ @current_database.find_view(name)
212
+ end
213
+
214
+ # --- Trigger Management ---
215
+
216
+ def create_trigger(name, event, table_name, definition, **options)
217
+ raise DatabaseError, "No database selected" unless @current_database
218
+
219
+ if @current_database.trigger_exists?(name)
220
+ raise DatabaseError, "Trigger '#{name}' already exists"
221
+ end
222
+
223
+ trigger = Trigger.new(name, event, table_name, definition, options)
224
+ @current_database.add_trigger(trigger)
225
+ @triggers[name] = trigger
226
+ @modified_at = Time.now
227
+ trigger
228
+ end
229
+
230
+ def drop_trigger(name, if_exists: false)
231
+ raise DatabaseError, "No database selected" unless @current_database
232
+
233
+ unless @current_database.trigger_exists?(name)
234
+ return if if_exists
235
+ raise DatabaseError, "Trigger '#{name}' does not exist"
236
+ end
237
+
238
+ trigger = @current_database.drop_trigger(name)
239
+ @triggers.delete(name)
240
+ @modified_at = Time.now
241
+ trigger
242
+ end
243
+
244
+ def find_trigger(name)
245
+ return nil unless @current_database
246
+ @current_database.find_trigger(name)
247
+ end
248
+
249
+ # --- Serialization ---
250
+
251
+ def serialize
252
+ data = {
253
+ version: @version,
254
+ created_at: @created_at.iso8601,
255
+ modified_at: @modified_at.iso8601,
256
+ current_database: @current_database&.name,
257
+ databases: @databases.transform_values(&:serialize),
258
+ system_catalog: @system_catalog.serialize
259
+ }
260
+ JSON.generate(data)
261
+ end
262
+
263
+ def self.deserialize(json_data)
264
+ data = JSON.parse(json_data, symbolize_names: true)
265
+ catalog = new
266
+
267
+ data[:databases].each do |name, db_data|
268
+ db = Database.deserialize(db_data)
269
+ catalog.databases[name.to_s] = db
270
+ end
271
+
272
+ if data[:current_database]
273
+ current = catalog.databases[data[:current_database]] || catalog.databases[data[:current_database].to_sym]
274
+ catalog.send(:current_database=, current)
275
+ end
276
+
277
+ # Rebuild indexes
278
+ catalog.databases.each do |_name, db|
279
+ db.tables.each do |table|
280
+ catalog.instance_variable_get(:@tables)[table.name] = table
281
+ table.columns.each do |col|
282
+ # Build indexes for columns
283
+ if col.primary_key?
284
+ index = Index.new("primary_#{table.name}", table.name, [col.name], unique: true)
285
+ table.add_index(index)
286
+ end
287
+ if col.unique?
288
+ index = Index.new("unique_#{table.name}_#{col.name}", table.name, [col.name], unique: true)
289
+ table.add_index(index)
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ catalog
296
+ end
297
+
298
+ protected
299
+
300
+ attr_writer :current_database
301
+ end
302
+
303
+ # Builder for creating tables with a DSL
304
+ class TableBuilder
305
+ attr_reader :table
306
+
307
+ def initialize(table)
308
+ @table = table
309
+ end
310
+
311
+ def column(name, type, **options)
312
+ @table.add_column(Column.new(name, type, **options))
313
+ end
314
+
315
+ def integer(name, **options)
316
+ column(name, :integer, **options)
317
+ end
318
+
319
+ def bigint(name, **options)
320
+ column(name, :bigint, **options)
321
+ end
322
+
323
+ def smallint(name, **options)
324
+ column(name, :smallint, **options)
325
+ end
326
+
327
+ def float(name, **options)
328
+ column(name, :float, **options)
329
+ end
330
+
331
+ def decimal(name, precision: 10, scale: 2, **options)
332
+ column(name, :decimal, precision: precision, scale: scale, **options)
333
+ end
334
+
335
+ def boolean(name, **options)
336
+ column(name, :boolean, **options)
337
+ end
338
+
339
+ def text(name, **options)
340
+ column(name, :text, **options)
341
+ end
342
+
343
+ def varchar(name, limit: 255, **options)
344
+ column(name, :varchar, limit: limit, **options)
345
+ end
346
+
347
+ def blob(name, **options)
348
+ column(name, :blob, **options)
349
+ end
350
+
351
+ def date(name, **options)
352
+ column(name, :date, **options)
353
+ end
354
+
355
+ def time(name, **options)
356
+ column(name, :time, **options)
357
+ end
358
+
359
+ def timestamp(name, **options)
360
+ column(name, :timestamp, **options)
361
+ end
362
+
363
+ def json(name, **options)
364
+ column(name, :json, **options)
365
+ end
366
+
367
+ def uuid(name, **options)
368
+ column(name, :uuid, **options)
369
+ end
370
+
371
+ def primary_key(name = :id, **options)
372
+ column(name, :integer, primary_key: true, **options)
373
+ end
374
+
375
+ def timestamps
376
+ timestamp(:created_at, default: "CURRENT_TIMESTAMP")
377
+ timestamp(:updated_at, default: "CURRENT_TIMESTAMP")
378
+ end
379
+
380
+ def index(name, **options)
381
+ @table.add_index(Index.new(name, @table.name, **options))
382
+ end
383
+
384
+ def foreign_key(column, ref_table, ref_column = :id, **options)
385
+ @table.add_constraint(
386
+ ForeignKeyConstraint.new(column, ref_table, ref_column, **options)
387
+ )
388
+ end
389
+ end
390
+ end
391
+ end