sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -0,0 +1,140 @@
1
+ # The server_block extension adds the Database#with_server method, which takes a shard
2
+ # argument and a block, and makes it so that access inside the block will use the
3
+ # specified shard by default.
4
+ #
5
+ # First, you need to enable it on the database object:
6
+ #
7
+ # DB.extension :server_block
8
+ #
9
+ # Then you can call with_server:
10
+ #
11
+ # DB.with_server(:shard1) do
12
+ # DB[:a].all # Uses shard1
13
+ # DB[:a].server(:shard2).all # Uses shard2
14
+ # end
15
+ # DB[:a].all # Uses default
16
+ #
17
+ # You can even nest calls to with_server:
18
+ #
19
+ # DB.with_server(:shard1) do
20
+ # DB[:a].all # Uses shard1
21
+ # DB.with_server(:shard2) do
22
+ # DB[:a].all # Uses shard2
23
+ # end
24
+ # DB[:a].all # Uses shard1
25
+ # end
26
+ # DB[:a].all # Uses default
27
+ #
28
+ # Note this this extension assumes the following shard names should use the
29
+ # server/shard passed to with_server: :default, nil, :read_only. All other
30
+ # shard names will cause the standard behavior to be used.
31
+
32
+ module Sequel
33
+ module ServerBlock
34
+ # Enable the server block on the connection pool, choosing the correct
35
+ # extension depending on whether the connection pool is threaded or not.
36
+ # Also defines the with_server method on the receiver for easy use.
37
+ def self.extended(db)
38
+ pool = db.pool
39
+ if defined?(ShardedThreadedConnectionPool) && pool.is_a?(ShardedThreadedConnectionPool)
40
+ pool.extend(ThreadedServerBlock)
41
+ pool.instance_variable_set(:@default_servers, {})
42
+ else
43
+ pool.extend(UnthreadedServerBlock)
44
+ pool.instance_variable_set(:@default_servers, [])
45
+ end
46
+ end
47
+
48
+ # Delegate to the connection pool
49
+ def with_server(server, &block)
50
+ pool.with_server(server, &block)
51
+ end
52
+ end
53
+
54
+ # Adds with_server support for the sharded single connection pool.
55
+ module UnthreadedServerBlock
56
+ # Set a default server/shard to use inside the block.
57
+ def with_server(server)
58
+ begin
59
+ set_default_server(server)
60
+ yield
61
+ ensure
62
+ clear_default_server
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # Make the given server the new default server.
69
+ def set_default_server(server)
70
+ @default_servers << server
71
+ end
72
+
73
+ # Remove the current default server, restoring the
74
+ # previous default server.
75
+ def clear_default_server
76
+ @default_servers.pop
77
+ end
78
+
79
+ # Use the server given to with_server if appropriate.
80
+ def pick_server(server)
81
+ if @default_servers.empty?
82
+ super
83
+ else
84
+ case server
85
+ when :default, nil, :read_only
86
+ @default_servers.last
87
+ else
88
+ super
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Adds with_server support for the sharded threaded connection pool.
95
+ module ThreadedServerBlock
96
+ # Set a default server/shard to use inside the block for the current
97
+ # thread.
98
+ def with_server(server)
99
+ begin
100
+ set_default_server(server)
101
+ yield
102
+ ensure
103
+ clear_default_server
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ # Make the given server the new default server for the current thread.
110
+ def set_default_server(server)
111
+ sync{(@default_servers[Thread.current] ||= [])} << server
112
+ end
113
+
114
+ # Remove the current default server for the current thread, restoring the
115
+ # previous default server.
116
+ def clear_default_server
117
+ t = Thread.current
118
+ a = sync{@default_servers[t]}
119
+ a.pop
120
+ sync{@default_servers.delete(t)} if a.empty?
121
+ end
122
+
123
+ # Use the server given to with_server for the given thread, if appropriate.
124
+ def pick_server(server)
125
+ a = sync{@default_servers[Thread.current]}
126
+ if !a || a.empty?
127
+ super
128
+ else
129
+ case server
130
+ when :default, nil, :read_only
131
+ a.last
132
+ else
133
+ super
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ Database.register_extension(:server_block, ServerBlock)
140
+ end
@@ -0,0 +1,65 @@
1
+ # The split_array_nil extension overrides Sequel's default handling of
2
+ # IN/NOT IN with arrays of values to do specific nil checking. For example,
3
+ #
4
+ # ds = DB[:table].where(:column=>[1, nil])
5
+ #
6
+ # By default, that produces the following SQL:
7
+ #
8
+ # SELECT * FROM table WHERE (column IN (1, NULL))
9
+ #
10
+ # However, because NULL = NULL is not true in SQL (it is NULL), this
11
+ # will not return rows in the table where the column is NULL. This
12
+ # extension allows for an alternative behavior more similar to ruby,
13
+ # which will return rows in the table where the column is NULL, using
14
+ # a query like:
15
+ #
16
+ # SELECT * FROM table WHERE ((column IN (1)) OR (column IS NULL)))
17
+ #
18
+ # Similarly, for NOT IN queries:
19
+ #
20
+ # ds = DB[:table].exclude(:column=>[1, nil])
21
+ # # Default:
22
+ # # SELECT * FROM table WHERE (column NOT IN (1, NULL))
23
+ # # with split_array_nils extension:
24
+ # # SELECT * FROM table WHERE ((column NOT IN (1)) AND (column IS NOT NULL)))
25
+ #
26
+ # To use this extension with a single dataset:
27
+ #
28
+ # ds = ds.extension(:split_array_nil)
29
+ #
30
+ # To use this extension for all of a database's datasets:
31
+ #
32
+ # DB.extension(:split_array_nil)
33
+
34
+ module Sequel
35
+ class Dataset
36
+ module SplitArrayNil
37
+ # Over the IN/NOT IN handling with an array of values where one of the
38
+ # values in the array is nil, by removing nils from the array of values,
39
+ # and using a separate OR IS NULL clause for IN or AND IS NOT NULL clause
40
+ # for NOT IN.
41
+ def complex_expression_sql_append(sql, op, args)
42
+ case op
43
+ when :IN, :"NOT IN"
44
+ vals = args.at(1)
45
+ if vals.is_a?(Array) && vals.any?{|v| v.nil?}
46
+ cols = args.at(0)
47
+ vals = vals.compact
48
+ c = Sequel::SQL::BooleanExpression
49
+ if op == :IN
50
+ literal_append(sql, c.new(:OR, c.new(:IN, cols, vals), c.new(:IS, cols, nil)))
51
+ else
52
+ literal_append(sql, c.new(:AND, c.new(:"NOT IN", cols, vals), c.new(:"IS NOT", cols, nil)))
53
+ end
54
+ else
55
+ super
56
+ end
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Dataset.register_extension(:split_array_nil, Dataset::SplitArrayNil)
65
+ end
@@ -1,122 +1,20 @@
1
1
  # The sql_expr extension adds the sql_expr method to every object, which
2
- # returns an object that works nicely with Sequel's DSL. This is
3
- # best shown by example:
2
+ # returns an wrapped object that works nicely with Sequel's DSL by calling
3
+ # Sequel.expr:
4
4
  #
5
5
  # 1.sql_expr < :a # 1 < a
6
6
  # false.sql_expr & :a # FALSE AND a
7
7
  # true.sql_expr | :a # TRUE OR a
8
8
  # ~nil.sql_expr # NOT NULL
9
9
  # "a".sql_expr + "b" # 'a' || 'b'
10
-
11
- module Sequel
12
- module SQL
13
- # The GenericComplexExpression acts like a
14
- # GenericExpression in terms of methods,
15
- # but has an internal structure of a
16
- # ComplexExpression. It is used by Object#sql_expr.
17
- # Since we don't know what specific type of object
18
- # we are dealing with it, we treat it similarly to
19
- # how we treat symbols or literal strings, allowing
20
- # many different types of methods.
21
- class GenericComplexExpression < ComplexExpression
22
- include AliasMethods
23
- include BooleanMethods
24
- include CastMethods
25
- include ComplexExpressionMethods
26
- include InequalityMethods
27
- include NumericMethods
28
- include OrderMethods
29
- include StringMethods
30
- include SubscriptMethods
31
- end
32
- end
33
- end
10
+ #
11
+ # To load the extension:
12
+ #
13
+ # Sequel.extension :sql_expr
34
14
 
35
15
  class Object
36
- # Return a copy of the object wrapped in a
37
- # Sequel::SQL::GenericComplexExpression. Allows easy use
38
- # of the Object with Sequel's DSL. You'll probably have
39
- # to make sure that Sequel knows how to literalize the
40
- # object properly, though.
41
- def sql_expr
42
- Sequel::SQL::GenericComplexExpression.new(:NOOP, self)
43
- end
44
- end
45
-
46
- class FalseClass
47
- # Returns a copy of the object wrapped in a
48
- # Sequel::SQL::BooleanExpression, allowing easy use
49
- # of Sequel's DSL:
50
- #
51
- # false.sql_expr & :a # FALSE AND a
52
- def sql_expr
53
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
54
- end
55
- end
56
-
57
- class NilClass
58
- # Returns a copy of the object wrapped in a
59
- # Sequel::SQL::BooleanExpression, allowing easy use
60
- # of Sequel's DSL:
61
- #
62
- # ~nil.sql_expr # NOT NULL
63
- def sql_expr
64
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
65
- end
66
- end
67
-
68
- class Numeric
69
- # Returns a copy of the object wrapped in a
70
- # Sequel::SQL::NumericExpression, allowing easy use
71
- # of Sequel's DSL:
72
- #
73
- # 1.sql_expr < :a # 1 < a
74
- def sql_expr
75
- Sequel::SQL::NumericExpression.new(:NOOP, self)
76
- end
77
- end
78
-
79
- class Proc
80
- # Evaluates the proc as a virtual row block.
81
- # If a hash or array of two element arrays is returned,
82
- # they are converted to a Sequel::SQL::BooleanExpression. Otherwise,
83
- # unless the object returned is already an Sequel::SQL::Expression,
84
- # convert the object to an Sequel::SQL::GenericComplexExpression.
85
- #
86
- # proc{a(b)}.sql_expr + 1 # a(b) + 1
87
- # proc{{a=>b}}.sql_expr | true # (a = b) OR TRUE
88
- # proc{1}.sql_expr + :a # 1 + a
16
+ # Return the object wrapper in an appropriate Sequel expression object.
89
17
  def sql_expr
90
- o = Sequel.virtual_row(&self)
91
- if Sequel.condition_specifier?(o)
92
- Sequel::SQL::BooleanExpression.from_value_pairs(o, :AND)
93
- elsif o.is_a?(Sequel::SQL::Expression)
94
- o
95
- else
96
- Sequel::SQL::GenericComplexExpression.new(:NOOP, o)
97
- end
18
+ Sequel.expr(self)
98
19
  end
99
20
  end
100
-
101
- class String
102
- # Returns a copy of the object wrapped in a
103
- # Sequel::SQL::StringExpression, allowing easy use
104
- # of Sequel's DSL:
105
- #
106
- # "a".sql_expr + :a # 'a' || a
107
- def sql_expr
108
- Sequel::SQL::StringExpression.new(:NOOP, self)
109
- end
110
- end
111
-
112
- class TrueClass
113
- # Returns a copy of the object wrapped in a
114
- # Sequel::SQL::BooleanExpression, allowing easy use
115
- # of Sequel's DSL:
116
- #
117
- # true.sql_expr | :a # TRUE OR a
118
- def sql_expr
119
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
120
- end
121
- end
122
-
@@ -1,6 +1,10 @@
1
1
  # The string_date_time extension provides String instance methods
2
2
  # for converting the strings to a date (e.g. String#to_date), allowing
3
3
  # for backwards compatibility with legacy Sequel code.
4
+ #
5
+ # To load the extension:
6
+ #
7
+ # Sequel.extension :string_date_time
4
8
 
5
9
  class String
6
10
  # Converts a string into a Date object.
@@ -1,9 +1,15 @@
1
1
  # The thread_local_timezones extension allows you to set a per-thread timezone that
2
2
  # will override the default global timezone while the thread is executing. The
3
3
  # main use case is for web applications that execute each request in its own thread,
4
- # and want to set the timezones based on the request. The most common example
5
- # is having the database always store time in UTC, but have the application deal
6
- # with the timezone of the current user. That can be done with:
4
+ # and want to set the timezones based on the request.
5
+ #
6
+ # To load the extension:
7
+ #
8
+ # Sequel.extension :thread_local_timezones
9
+ #
10
+ # The most common example is having the database always store time in
11
+ # UTC, but have the application deal with the timezone of the current
12
+ # user. That can be done with:
7
13
  #
8
14
  # Sequel.database_timezone = :utc
9
15
  # # In each thread:
@@ -32,7 +38,7 @@ module Sequel
32
38
  module ThreadLocalTimezones
33
39
  %w'application database typecast'.each do |t|
34
40
  class_eval("def thread_#{t}_timezone=(tz); Thread.current[:#{t}_timezone] = convert_timezone_setter_arg(tz); end", __FILE__, __LINE__)
35
- class_eval(<<END, __FILE__, __LINE__)
41
+ class_eval(<<END, __FILE__, __LINE__ + 1)
36
42
  def #{t}_timezone
37
43
  if tz = Thread.current[:#{t}_timezone]
38
44
  tz unless tz == :nil
@@ -2,111 +2,117 @@
2
2
  # returns a string that can be processed by graphviz's +dot+ program in
3
3
  # order to get a visualization of the dataset. Basically, it shows a version
4
4
  # of the dataset's abstract syntax tree.
5
+ #
6
+ # To load the extension:
7
+ #
8
+ # Sequel.extension :to_dot
5
9
 
6
10
  module Sequel
7
- class Dataset
11
+ class ToDot
8
12
  # The option keys that should be included in the dot output.
9
13
  TO_DOT_OPTIONS = [:with, :distinct, :select, :from, :join, :where, :group, :having, :compounds, :order, :limit, :offset, :lock].freeze
10
14
 
11
- # Return a string that can be processed by the +dot+ program (included
12
- # with graphviz) in order to see a visualization of the dataset's
13
- # abstract syntax tree.
14
- def to_dot
15
- i = 0
16
- dot = ["digraph G {", "#{i} [label=\"self\"];"]
17
- _to_dot(dot, "", i, self, i)
18
- dot << "}"
19
- dot.join("\n")
15
+ # Given a +Dataset+, return a string in +dot+ format that will
16
+ # generate a visualization of the dataset.
17
+ def self.output(ds)
18
+ new(ds).output
19
+ end
20
+
21
+ # Given a +Dataset+, parse the internal structure to generate
22
+ # a dataset visualization.
23
+ def initialize(ds)
24
+ @i = 0
25
+ @stack = [@i]
26
+ @dot = ["digraph G {", "0 [label=\"self\"];"]
27
+ v(ds, "")
28
+ @dot << "}"
29
+ end
30
+
31
+ # Output the dataset visualization as a string in +dot+ format.
32
+ def output
33
+ @dot.join("\n")
20
34
  end
21
35
 
22
36
  private
23
37
 
24
- # Internal recursive version that handles all object types understood
25
- # by Sequel. Arguments:
26
- # * dot :: An array of strings representing the lines in the returned
27
- # output. This function just pushes strings onto this array.
28
- # * l :: The transition label from the parent node of the AST to the
29
- # current node.
30
- # * c :: An integer representing the parent node of the AST.
31
- # * e :: The current node of the AST.
32
- # * i :: The integer representing the last created node of the AST.
33
- #
34
- # The basic algorithm is that the +i+ is incremented to get the current
35
- # node's integer. Then the transition from the parent node to the
36
- # current node is added to the +dot+ array. Finally, the current node
37
- # is added to the +dot+ array, and if it is a compound node with children,
38
- # its children are then added by recursively calling this method. The
39
- # return value is the integer representing the last created node.
40
- def _to_dot(dot, l, c, e, i)
41
- i += 1
42
- dot << "#{c} -> #{i} [label=\"#{l}\"];" if l
43
- c = i
38
+ # Add an entry to the +dot+ output with the given label. If +j+
39
+ # is given, it is used directly as the node or transition. Otherwise
40
+ # a node is created for the current object.
41
+ def dot(label, j=nil)
42
+ @dot << "#{j||@i} [label=#{label.to_s.inspect}];"
43
+ end
44
+
45
+ # Recursive method that parses all of Sequel's internal datastructures,
46
+ # adding the appropriate nodes and transitions to the internal +dot+
47
+ # structure.
48
+ def v(e, l)
49
+ @i += 1
50
+ dot(l, "#{@stack.last} -> #{@i}") if l
51
+ @stack.push(@i)
44
52
  case e
45
53
  when LiteralString
46
- dot << "#{i} [label=\"#{e.inspect.gsub('"', '\\"')}.lit\"];"
47
- i
54
+ dot "#{e.inspect}.lit" # core_sql use
48
55
  when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
49
- dot << "#{i} [label=\"#{e.inspect.gsub('"', '\\"')}\"];"
50
- i
56
+ dot e.inspect
51
57
  when Array
52
- dot << "#{i} [label=\"Array\"];"
53
- e.each_with_index do |v, j|
54
- i = _to_dot(dot, j, c, v, i)
58
+ dot "Array"
59
+ e.each_with_index do |val, j|
60
+ v(val, j)
55
61
  end
56
62
  when Hash
57
- dot << "#{i} [label=\"Hash\"];"
58
- e.each do |k, v|
59
- i = _to_dot(dot, k, c, v, i)
63
+ dot "Hash"
64
+ e.each do |k, val|
65
+ v(val, k)
60
66
  end
61
67
  when SQL::ComplexExpression
62
- dot << "#{i} [label=\"ComplexExpression: #{e.op}\"];"
63
- e.args.each_with_index do |v, j|
64
- i = _to_dot(dot, j, c, v, i)
68
+ dot "ComplexExpression: #{e.op}"
69
+ e.args.each_with_index do |val, j|
70
+ v(val, j)
65
71
  end
66
72
  when SQL::Identifier
67
- dot << "#{i} [label=\"Identifier\"];"
68
- i = _to_dot(dot, :value, c, e.value, i)
73
+ dot "Identifier"
74
+ v(e.value, :value)
69
75
  when SQL::QualifiedIdentifier
70
- dot << "#{i} [label=\"QualifiedIdentifier\"];"
71
- i = _to_dot(dot, :table, c, e.table, i)
72
- i = _to_dot(dot, :column, c, e.column, i)
76
+ dot "QualifiedIdentifier"
77
+ v(e.table, :table)
78
+ v(e.column, :column)
73
79
  when SQL::OrderedExpression
74
- dot << "#{i} [label=\"OrderedExpression: #{e.descending ? :DESC : :ASC}#{" NULLS #{e.nulls.to_s.upcase}" if e.nulls}\"];"
75
- i = _to_dot(dot, :expression, c, e.expression, i)
80
+ dot "OrderedExpression: #{e.descending ? :DESC : :ASC}#{" NULLS #{e.nulls.to_s.upcase}" if e.nulls}"
81
+ v(e.expression, :expression)
76
82
  when SQL::AliasedExpression
77
- dot << "#{i} [label=\"AliasedExpression\"];"
78
- i = _to_dot(dot, :expression, c, e.expression, i)
79
- i = _to_dot(dot, :alias, c, e.aliaz, i)
83
+ dot "AliasedExpression"
84
+ v(e.expression, :expression)
85
+ v(e.aliaz, :alias)
80
86
  when SQL::CaseExpression
81
- dot << "#{i} [label=\"CaseExpression\"];"
82
- i = _to_dot(dot, :expression, c, e.expression, i) if e.expression
83
- i = _to_dot(dot, :conditions, c, e.conditions, i)
84
- i = _to_dot(dot, :default, c, e.default, i)
87
+ dot "CaseExpression"
88
+ v(e.expression, :expression) if e.expression
89
+ v(e.conditions, :conditions)
90
+ v(e.default, :default)
85
91
  when SQL::Cast
86
- dot << "#{i} [label=\"Cast\"];"
87
- i = _to_dot(dot, :expr, c, e.expr, i)
88
- i = _to_dot(dot, :type, c, e.type, i)
92
+ dot "Cast"
93
+ v(e.expr, :expr)
94
+ v(e.type, :type)
89
95
  when SQL::Function
90
- dot << "#{i} [label=\"Function: #{e.f}\"];"
91
- e.args.each_with_index do |v, j|
92
- i = _to_dot(dot, j, c, v, i)
96
+ dot "Function: #{e.f}"
97
+ e.args.each_with_index do |val, j|
98
+ v(val, j)
93
99
  end
94
100
  when SQL::Subscript
95
- dot << "#{i} [label=\"Subscript: #{e.f}\"];"
96
- i = _to_dot(dot, :f, c, e.f, i)
97
- i = _to_dot(dot, :sub, c, e.sub, i)
101
+ dot "Subscript"
102
+ v(e.f, :f)
103
+ v(e.sub, :sub)
98
104
  when SQL::WindowFunction
99
- dot << "#{i} [label=\"WindowFunction\"];"
100
- i = _to_dot(dot, :function, c, e.function, i)
101
- i = _to_dot(dot, :window, c, e.window, i)
105
+ dot "WindowFunction"
106
+ v(e.function, :function)
107
+ v(e.window, :window)
102
108
  when SQL::Window
103
- dot << "#{i} [label=\"Window\"];"
104
- i = _to_dot(dot, :opts, c, e.opts, i)
109
+ dot "Window"
110
+ v(e.opts, :opts)
105
111
  when SQL::PlaceholderLiteralString
106
112
  str = e.str
107
113
  str = "(#{str})" if e.parens
108
- dot << "#{i} [label=\"PlaceholderLiteralString: #{str.inspect.gsub('"', '\\"')}\"];"
109
- i = _to_dot(dot, :args, c, e.args, i)
114
+ dot "PlaceholderLiteralString: #{str.inspect}"
115
+ v(e.args, :args)
110
116
  when SQL::JoinClause
111
117
  str = "#{e.join_type.to_s.upcase} JOIN"
112
118
  if e.is_a?(SQL::JoinOnClause)
@@ -114,24 +120,34 @@ module Sequel
114
120
  elsif e.is_a?(SQL::JoinUsingClause)
115
121
  str << " USING"
116
122
  end
117
- dot << "#{i} [label=\"#{str}\"];"
118
- i = _to_dot(dot, :table, c, e.table, i)
119
- i = _to_dot(dot, :alias, c, e.table_alias, i) if e.table_alias
123
+ dot str
124
+ v(e.table, :table)
125
+ v(e.table_alias, :alias) if e.table_alias
120
126
  if e.is_a?(SQL::JoinOnClause)
121
- i = _to_dot(dot, :on, c, e.on, i)
127
+ v(e.on, :on)
122
128
  elsif e.is_a?(SQL::JoinUsingClause)
123
- i = _to_dot(dot, :using, c, e.using, i)
129
+ v(e.using, :using)
124
130
  end
125
131
  when Dataset
126
- dot << "#{i} [label=\"Dataset\"];"
132
+ dot "Dataset"
127
133
  TO_DOT_OPTIONS.each do |k|
128
- next unless e.opts[k]
129
- i = _to_dot(dot, k, c, e.opts[k], i)
134
+ if val = e.opts[k]
135
+ v(val, k.to_s)
136
+ end
130
137
  end
131
138
  else
132
- dot << "#{i} [label=\"Unhandled: #{e.inspect.gsub('"', "''")}\"];"
139
+ dot "Unhandled: #{e.inspect}"
133
140
  end
134
- i
141
+ @stack.pop
142
+ end
143
+ end
144
+
145
+ class Dataset
146
+ # Return a string that can be processed by the +dot+ program (included
147
+ # with graphviz) in order to see a visualization of the dataset's
148
+ # abstract syntax tree.
149
+ def to_dot
150
+ ToDot.output(self)
135
151
  end
136
152
  end
137
153
  end