synthra 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 (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. metadata +478 -0
@@ -0,0 +1,381 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Exports schema as SQL CREATE TABLE statements
7
+ #
8
+ # @example Export a schema
9
+ # exporter = Sql.new(schema, registry: registry, dialect: :postgresql)
10
+ # sql = exporter.export
11
+ #
12
+ class Sql < Base
13
+ # Supported SQL dialects
14
+ DIALECTS = %i[postgresql mysql sqlite].freeze
15
+
16
+ # Type mappings for PostgreSQL
17
+ POSTGRESQL_TYPES = {
18
+ "uuid" => "UUID",
19
+ "ulid" => "VARCHAR(26)",
20
+ "email" => "VARCHAR(255)",
21
+ "url" => "TEXT",
22
+ "ip" => "INET",
23
+ "ipv6" => "INET",
24
+ "date" => "DATE",
25
+ "date_between" => "DATE",
26
+ "past_date" => "DATE",
27
+ "future_date" => "DATE",
28
+ "mobile_device_release_date" => "DATE",
29
+ "now" => "DATE",
30
+ "timestamp" => "TIMESTAMP WITH TIME ZONE",
31
+ "datetime" => "TIMESTAMP WITH TIME ZONE",
32
+ "text" => "TEXT",
33
+ "name" => "VARCHAR(255)",
34
+ "full_name" => "VARCHAR(255)",
35
+ "first_name" => "VARCHAR(100)",
36
+ "last_name" => "VARCHAR(100)",
37
+ "phone" => "VARCHAR(50)",
38
+ "city" => "VARCHAR(100)",
39
+ "country" => "VARCHAR(100)",
40
+ "country_code" => "CHAR(3)",
41
+ "postal_code" => "VARCHAR(20)",
42
+ "state" => "VARCHAR(100)",
43
+ "street" => "TEXT",
44
+ "address" => "TEXT",
45
+ "username" => "VARCHAR(50)",
46
+ "social_handle" => "VARCHAR(50)",
47
+ "domain" => "VARCHAR(255)",
48
+ "user_agent" => "TEXT",
49
+ "mac_address" => "MACADDR",
50
+ "iban" => "VARCHAR(34)",
51
+ "credit_card" => "VARCHAR(20)",
52
+ "currency" => "VARCHAR(10)",
53
+ "currency_code" => "CHAR(3)",
54
+ "paragraph" => "TEXT",
55
+ "sentence" => "TEXT",
56
+ "word" => "VARCHAR(100)",
57
+ "message_text" => "TEXT",
58
+ "hashtag_text" => "VARCHAR(100)",
59
+ "snowflake_id" => "BIGINT",
60
+ "numeric_string_id" => "BIGINT",
61
+ "number" => "INTEGER",
62
+ "integer" => "INTEGER",
63
+ "age" => "INTEGER",
64
+ "airport_elevation" => "INTEGER",
65
+ "discount" => "INTEGER",
66
+ "formula" => "INTEGER",
67
+ "row_number" => "INTEGER",
68
+ "sequence" => "INTEGER",
69
+ "float" => "DECIMAL(15,2)",
70
+ "money" => "DECIMAL(15,2)",
71
+ "latitude" => "DECIMAL(10,8)",
72
+ "longitude" => "DECIMAL(11,8)",
73
+ "airport_latitude" => "DECIMAL(10,8)",
74
+ "airport_longitude" => "DECIMAL(11,8)",
75
+ "product_price" => "DECIMAL(15,2)",
76
+ "tax_rate" => "DECIMAL(10,4)",
77
+ "transaction_amount" => "DECIMAL(15,2)",
78
+ "id_sequence" => "SERIAL",
79
+ "boolean" => "BOOLEAN",
80
+ "object" => "JSONB",
81
+ "map_by_field" => "JSONB",
82
+ "array" => "JSONB",
83
+ "empty_array" => "JSONB",
84
+ "json_array" => "JSONB",
85
+ "indices_pair" => "INTEGER[]"
86
+ }.freeze
87
+
88
+ # Type mappings for MySQL
89
+ MYSQL_TYPES = {
90
+ "uuid" => "CHAR(36)",
91
+ "ulid" => "CHAR(26)",
92
+ "email" => "VARCHAR(255)",
93
+ "url" => "TEXT",
94
+ "ip" => "VARCHAR(45)",
95
+ "ipv6" => "VARCHAR(45)",
96
+ "date" => "DATE",
97
+ "date_between" => "DATE",
98
+ "past_date" => "DATE",
99
+ "future_date" => "DATE",
100
+ "mobile_device_release_date" => "DATE",
101
+ "now" => "DATE",
102
+ "timestamp" => "DATETIME",
103
+ "datetime" => "DATETIME",
104
+ "text" => "TEXT",
105
+ "name" => "VARCHAR(255)",
106
+ "full_name" => "VARCHAR(255)",
107
+ "first_name" => "VARCHAR(100)",
108
+ "last_name" => "VARCHAR(100)",
109
+ "phone" => "VARCHAR(50)",
110
+ "city" => "VARCHAR(100)",
111
+ "country" => "VARCHAR(100)",
112
+ "country_code" => "CHAR(3)",
113
+ "postal_code" => "VARCHAR(20)",
114
+ "state" => "VARCHAR(100)",
115
+ "street" => "TEXT",
116
+ "address" => "TEXT",
117
+ "username" => "VARCHAR(50)",
118
+ "social_handle" => "VARCHAR(50)",
119
+ "domain" => "VARCHAR(255)",
120
+ "user_agent" => "TEXT",
121
+ "mac_address" => "CHAR(17)",
122
+ "iban" => "VARCHAR(34)",
123
+ "credit_card" => "VARCHAR(20)",
124
+ "currency" => "VARCHAR(10)",
125
+ "currency_code" => "CHAR(3)",
126
+ "paragraph" => "TEXT",
127
+ "sentence" => "TEXT",
128
+ "word" => "VARCHAR(100)",
129
+ "message_text" => "TEXT",
130
+ "hashtag_text" => "VARCHAR(100)",
131
+ "snowflake_id" => "BIGINT",
132
+ "numeric_string_id" => "BIGINT",
133
+ "number" => "INT",
134
+ "integer" => "INT",
135
+ "age" => "INT",
136
+ "airport_elevation" => "INT",
137
+ "discount" => "INT",
138
+ "formula" => "INT",
139
+ "row_number" => "INT",
140
+ "sequence" => "INT",
141
+ "float" => "DECIMAL(15,2)",
142
+ "money" => "DECIMAL(15,2)",
143
+ "latitude" => "DECIMAL(10,8)",
144
+ "longitude" => "DECIMAL(11,8)",
145
+ "airport_latitude" => "DECIMAL(10,8)",
146
+ "airport_longitude" => "DECIMAL(11,8)",
147
+ "product_price" => "DECIMAL(15,2)",
148
+ "tax_rate" => "DECIMAL(10,4)",
149
+ "transaction_amount" => "DECIMAL(15,2)",
150
+ "id_sequence" => "INT AUTO_INCREMENT",
151
+ "boolean" => "TINYINT(1)",
152
+ "object" => "JSON",
153
+ "map_by_field" => "JSON",
154
+ "array" => "JSON",
155
+ "empty_array" => "JSON",
156
+ "json_array" => "JSON",
157
+ "indices_pair" => "JSON"
158
+ }.freeze
159
+
160
+ # Type mappings for SQLite
161
+ SQLITE_TYPES = {
162
+ "uuid" => "TEXT",
163
+ "ulid" => "TEXT",
164
+ "email" => "TEXT",
165
+ "url" => "TEXT",
166
+ "ip" => "TEXT",
167
+ "ipv6" => "TEXT",
168
+ "date" => "TEXT",
169
+ "date_between" => "TEXT",
170
+ "past_date" => "TEXT",
171
+ "future_date" => "TEXT",
172
+ "mobile_device_release_date" => "TEXT",
173
+ "now" => "TEXT",
174
+ "timestamp" => "TEXT",
175
+ "datetime" => "TEXT",
176
+ "text" => "TEXT",
177
+ "name" => "TEXT",
178
+ "full_name" => "TEXT",
179
+ "first_name" => "TEXT",
180
+ "last_name" => "TEXT",
181
+ "phone" => "TEXT",
182
+ "city" => "TEXT",
183
+ "country" => "TEXT",
184
+ "country_code" => "TEXT",
185
+ "postal_code" => "TEXT",
186
+ "state" => "TEXT",
187
+ "street" => "TEXT",
188
+ "address" => "TEXT",
189
+ "username" => "TEXT",
190
+ "social_handle" => "TEXT",
191
+ "domain" => "TEXT",
192
+ "user_agent" => "TEXT",
193
+ "mac_address" => "TEXT",
194
+ "iban" => "TEXT",
195
+ "credit_card" => "TEXT",
196
+ "currency" => "TEXT",
197
+ "currency_code" => "TEXT",
198
+ "paragraph" => "TEXT",
199
+ "sentence" => "TEXT",
200
+ "word" => "TEXT",
201
+ "message_text" => "TEXT",
202
+ "hashtag_text" => "TEXT",
203
+ "snowflake_id" => "INTEGER",
204
+ "numeric_string_id" => "INTEGER",
205
+ "number" => "INTEGER",
206
+ "integer" => "INTEGER",
207
+ "age" => "INTEGER",
208
+ "airport_elevation" => "INTEGER",
209
+ "discount" => "INTEGER",
210
+ "formula" => "INTEGER",
211
+ "row_number" => "INTEGER",
212
+ "sequence" => "INTEGER",
213
+ "float" => "REAL",
214
+ "money" => "REAL",
215
+ "latitude" => "REAL",
216
+ "longitude" => "REAL",
217
+ "airport_latitude" => "REAL",
218
+ "airport_longitude" => "REAL",
219
+ "product_price" => "REAL",
220
+ "tax_rate" => "REAL",
221
+ "transaction_amount" => "REAL",
222
+ "id_sequence" => "INTEGER PRIMARY KEY AUTOINCREMENT",
223
+ "boolean" => "INTEGER",
224
+ "object" => "TEXT",
225
+ "map_by_field" => "TEXT",
226
+ "array" => "TEXT",
227
+ "empty_array" => "TEXT",
228
+ "json_array" => "TEXT",
229
+ "indices_pair" => "TEXT"
230
+ }.freeze
231
+
232
+ def export
233
+ dialect = options[:dialect] || :postgresql
234
+ lines = []
235
+
236
+ # Add header comment
237
+ lines << "-- Generated from Synthra schema: #{schema.name}"
238
+ lines << "-- Dialect: #{dialect.to_s.upcase}"
239
+ lines << "-- Version: #{schema.version}" if schema.version
240
+ lines << ""
241
+
242
+ # Export referenced schemas first
243
+ exported = Set.new
244
+ collect_referenced_schemas.each do |ref_schema|
245
+ lines << build_table(ref_schema, dialect)
246
+ lines << ""
247
+ exported << ref_schema.name
248
+ end
249
+
250
+ # Export main schema
251
+ lines << build_table(schema, dialect) unless exported.include?(schema.name)
252
+
253
+ lines.join("\n")
254
+ end
255
+
256
+ # Export all schemas in a registry
257
+ #
258
+ # @param registry [Registry] the registry
259
+ # @param options [Hash] export options
260
+ # @return [String] SQL statements
261
+ #
262
+ def self.export_all(registry, **options)
263
+ dialect = options[:dialect] || :postgresql
264
+ lines = []
265
+ lines << "-- Generated from Synthra schemas"
266
+ lines << "-- Dialect: #{dialect.to_s.upcase}"
267
+ lines << "-- Schemas: #{registry.names.join(', ')}"
268
+ lines << ""
269
+
270
+ registry.schemas.each do |name, schema|
271
+ exporter = new(schema, registry: registry, **options)
272
+ lines << exporter.build_table(schema, dialect)
273
+ lines << ""
274
+ end
275
+
276
+ lines.join("\n")
277
+ end
278
+
279
+ def build_table(target_schema, dialect)
280
+ type_mappings = case dialect
281
+ when :mysql then MYSQL_TYPES
282
+ when :sqlite then SQLITE_TYPES
283
+ else POSTGRESQL_TYPES
284
+ end
285
+
286
+ table_name = to_snake_case(target_schema.name)
287
+ lines = []
288
+
289
+ lines << "CREATE TABLE #{table_name} ("
290
+
291
+ columns = target_schema.fields.map do |field|
292
+ build_column(field, type_mappings, dialect)
293
+ end
294
+
295
+ lines << " " + columns.join(",\n ")
296
+ lines << ");"
297
+
298
+ lines.join("\n")
299
+ end
300
+
301
+ private
302
+
303
+ def collect_referenced_schemas
304
+ schemas = []
305
+ schema.fields.each do |field|
306
+ collect_field_schemas(field, schemas)
307
+ end
308
+ schemas.uniq(&:name)
309
+ end
310
+
311
+ def collect_field_schemas(field, schemas)
312
+ type_name = field.type_name
313
+
314
+ if schema_reference?(type_name)
315
+ ref_schema = get_schema(type_name)
316
+ if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
317
+ schemas << ref_schema
318
+ end
319
+ end
320
+
321
+ if type_name == "array"
322
+ element = field.type_args[:element]
323
+ if element && schema_reference?(element.to_s)
324
+ ref_schema = get_schema(element.to_s)
325
+ if ref_schema && !schemas.any? { |s| s.name == ref_schema.name }
326
+ schemas << ref_schema
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ def build_column(field, type_mappings, dialect)
333
+ column_name = to_snake_case(field.name)
334
+ sql_type = field_to_sql_type(field, type_mappings)
335
+
336
+ parts = [column_name, sql_type]
337
+ parts << "NOT NULL" unless field.optional? || field.nullable?
338
+ parts << "DEFAULT NULL" if field.nullable? && !field.optional?
339
+
340
+ parts.join(" ")
341
+ end
342
+
343
+ def field_to_sql_type(field, type_mappings)
344
+ type_name = field.type_name
345
+ type_args = field.type_args
346
+
347
+ case type_name
348
+ when "enum"
349
+ build_enum_type(type_args, type_mappings)
350
+ when "const"
351
+ type_mappings["text"]
352
+ when "array", "one_of", "map_by_field"
353
+ type_mappings["object"] || "TEXT"
354
+ else
355
+ if type_mappings[type_name]
356
+ type_mappings[type_name]
357
+ elsif schema_reference?(type_name)
358
+ # Foreign key - use UUID or appropriate ID type
359
+ type_mappings["uuid"] || "TEXT"
360
+ else
361
+ type_mappings["text"] || "TEXT"
362
+ end
363
+ end
364
+ end
365
+
366
+ def build_enum_type(args, type_mappings)
367
+ values = args[:values]&.map { |v| v.respond_to?(:value) ? v.value : v } || []
368
+ # For PostgreSQL, could create an ENUM type, but VARCHAR is more portable
369
+ max_length = values.map(&:to_s).map(&:length).max || 50
370
+ "VARCHAR(#{[max_length, 50].max})"
371
+ end
372
+
373
+ def to_snake_case(str)
374
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
375
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
376
+ .downcase
377
+ end
378
+ end
379
+ # :nocov:
380
+ end
381
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Synthra
5
+ module Export
6
+ # Exports generated data as SQL INSERT statements
7
+ #
8
+ # @example Export schema data as SQL INSERTs
9
+ # exporter = SqlInsert.new(schema, registry: registry, count: 100, dialect: :postgresql)
10
+ # sql = exporter.export
11
+ #
12
+ class SqlInsert < Base
13
+ # Supported SQL dialects
14
+ DIALECTS = %i[postgresql mysql sqlite].freeze
15
+
16
+ def export
17
+ count = options[:count] || 10
18
+ seed = options[:seed]
19
+ mode = options[:mode] || :random
20
+ dialect = options[:dialect] || :postgresql
21
+
22
+ records = schema.generate_many(count, seed: seed, mode: mode, registry: registry)
23
+
24
+ lines = []
25
+ lines << "-- Generated from Synthra schema: #{schema.name}"
26
+ lines << "-- Dialect: #{dialect.to_s.upcase}"
27
+ lines << "-- Records: #{count}"
28
+ lines << "-- Generated at: #{Time.now.iso8601}"
29
+ lines << ""
30
+
31
+ table_name = to_snake_case(schema.name)
32
+
33
+ if options[:batch_insert] && dialect != :sqlite
34
+ lines << build_batch_insert(table_name, records, dialect)
35
+ else
36
+ records.each do |record|
37
+ lines << build_single_insert(table_name, record, dialect)
38
+ end
39
+ end
40
+
41
+ lines.join("\n")
42
+ end
43
+
44
+ private
45
+
46
+ def build_single_insert(table_name, record, dialect)
47
+ flat_record = flatten_record(record)
48
+ columns = flat_record.keys.map { |k| quote_identifier(k, dialect) }
49
+ values = flat_record.values.map { |v| format_value(v, dialect) }
50
+
51
+ "INSERT INTO #{quote_identifier(table_name, dialect)} (#{columns.join(', ')}) VALUES (#{values.join(', ')});"
52
+ end
53
+
54
+ def build_batch_insert(table_name, records, dialect)
55
+ return "" if records.empty?
56
+
57
+ flat_records = records.map { |r| flatten_record(r) }
58
+ columns = flat_records.first.keys.map { |k| quote_identifier(k, dialect) }
59
+
60
+ lines = []
61
+ lines << "INSERT INTO #{quote_identifier(table_name, dialect)} (#{columns.join(', ')}) VALUES"
62
+
63
+ values_lines = flat_records.map do |flat_record|
64
+ values = flat_record.values.map { |v| format_value(v, dialect) }
65
+ " (#{values.join(', ')})"
66
+ end
67
+
68
+ lines << values_lines.join(",\n") + ";"
69
+ lines.join("\n")
70
+ end
71
+
72
+ def flatten_record(record, prefix = "")
73
+ result = {}
74
+
75
+ record.each do |key, value|
76
+ full_key = prefix.empty? ? key : "#{prefix}_#{key}"
77
+
78
+ case value
79
+ when Hash
80
+ # For nested objects, either flatten or serialize as JSON
81
+ if options[:flatten_nested]
82
+ result.merge!(flatten_record(value, full_key))
83
+ else
84
+ result[full_key] = JSON.generate(value)
85
+ end
86
+ when Array
87
+ # Arrays become JSON
88
+ result[full_key] = JSON.generate(value)
89
+ else
90
+ result[full_key] = value
91
+ end
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ def format_value(value, dialect)
98
+ case value
99
+ when nil
100
+ "NULL"
101
+ when true
102
+ dialect == :mysql ? "1" : "TRUE"
103
+ when false
104
+ dialect == :mysql ? "0" : "FALSE"
105
+ when Integer
106
+ value.to_s
107
+ when Float
108
+ value.to_s
109
+ when String
110
+ escape_string(value, dialect)
111
+ when Time, DateTime
112
+ escape_string(value.iso8601, dialect)
113
+ when Date
114
+ escape_string(value.to_s, dialect)
115
+ else
116
+ escape_string(value.to_s, dialect)
117
+ end
118
+ end
119
+
120
+ def escape_string(str, dialect)
121
+ escaped = str.gsub("'", "''")
122
+ case dialect
123
+ when :mysql
124
+ "'#{escaped.gsub('\\', '\\\\\\\\')}'"
125
+ else
126
+ "'#{escaped}'"
127
+ end
128
+ end
129
+
130
+ def quote_identifier(name, dialect)
131
+ snake_name = to_snake_case(name.to_s)
132
+ # SECURITY: escape the quote char inside the identifier (double it) so a field name
133
+ # containing a backtick / double-quote can't break out of the quoting into injected SQL.
134
+ case dialect
135
+ when :mysql
136
+ "`#{snake_name.gsub('`', '``')}`"
137
+ when :postgresql
138
+ "\"#{snake_name.gsub('"', '""')}\""
139
+ else
140
+ snake_name
141
+ end
142
+ end
143
+
144
+ def to_snake_case(str)
145
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
146
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
147
+ .downcase
148
+ end
149
+ end
150
+ # :nocov:
151
+ end
152
+ end