plume 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +506 -11
  3. data/lib/plume/analysis/resolver.rb +797 -0
  4. data/lib/plume/analysis.rb +211 -0
  5. data/lib/plume/ast/alter_table_statement.rb +40 -0
  6. data/lib/plume/ast/analyze_statement.rb +15 -0
  7. data/lib/plume/ast/assignment.rb +18 -0
  8. data/lib/plume/ast/attach_statement.rb +17 -0
  9. data/lib/plume/ast/begin_statement.rb +17 -0
  10. data/lib/plume/ast/binary_expression.rb +71 -0
  11. data/lib/plume/ast/case_condition.rb +29 -0
  12. data/lib/plume/ast/case_expression.rb +22 -0
  13. data/lib/plume/ast/cast_expression.rb +16 -0
  14. data/lib/plume/ast/check_column_constraint.rb +20 -0
  15. data/lib/plume/ast/check_table_constraint.rb +20 -0
  16. data/lib/plume/ast/collate_column_constraint.rb +19 -0
  17. data/lib/plume/ast/collation_expression.rb +26 -0
  18. data/lib/plume/ast/column_constraint.rb +7 -0
  19. data/lib/plume/ast/column_definition.rb +16 -0
  20. data/lib/plume/ast/column_name.rb +22 -0
  21. data/lib/plume/ast/column_type.rb +26 -0
  22. data/lib/plume/ast/commit_statement.rb +13 -0
  23. data/lib/plume/ast/common_table_expression.rb +31 -0
  24. data/lib/plume/ast/compound_select.rb +30 -0
  25. data/lib/plume/ast/conflict_clause.rb +22 -0
  26. data/lib/plume/ast/conflict_resolution.rb +20 -0
  27. data/lib/plume/ast/create_index_statement.rb +29 -0
  28. data/lib/plume/ast/create_table_statement.rb +47 -0
  29. data/lib/plume/ast/create_trigger_statement.rb +43 -0
  30. data/lib/plume/ast/create_view_statement.rb +30 -0
  31. data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
  32. data/lib/plume/ast/default_column_constraint.rb +29 -0
  33. data/lib/plume/ast/deferrable_column_constraint.rb +20 -0
  34. data/lib/plume/ast/delete_statement.rb +28 -0
  35. data/lib/plume/ast/detach_statement.rb +13 -0
  36. data/lib/plume/ast/drop_statement.rb +21 -0
  37. data/lib/plume/ast/empty_function_argument.rb +6 -0
  38. data/lib/plume/ast/exists_expression.rb +15 -0
  39. data/lib/plume/ast/explain_statement.rb +15 -0
  40. data/lib/plume/ast/expression.rb +30 -0
  41. data/lib/plume/ast/foreign_key_action.rb +26 -0
  42. data/lib/plume/ast/foreign_key_clause.rb +50 -0
  43. data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
  44. data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
  45. data/lib/plume/ast/frame_boundary.rb +35 -0
  46. data/lib/plume/ast/frame_spec.rb +34 -0
  47. data/lib/plume/ast/function_arguments.rb +18 -0
  48. data/lib/plume/ast/function_reference.rb +29 -0
  49. data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
  50. data/lib/plume/ast/indexed_column.rb +24 -0
  51. data/lib/plume/ast/insert_statement.rb +35 -0
  52. data/lib/plume/ast/join.rb +12 -0
  53. data/lib/plume/ast/join_clause.rb +15 -0
  54. data/lib/plume/ast/join_on_constraint.rb +12 -0
  55. data/lib/plume/ast/join_operator.rb +31 -0
  56. data/lib/plume/ast/join_using_constraint.rb +16 -0
  57. data/lib/plume/ast/like_expression.rb +19 -0
  58. data/lib/plume/ast/literal_expression.rb +29 -0
  59. data/lib/plume/ast/literal_false.rb +7 -0
  60. data/lib/plume/ast/literal_nil.rb +7 -0
  61. data/lib/plume/ast/literal_value.rb +18 -0
  62. data/lib/plume/ast/match_clause.rb +15 -0
  63. data/lib/plume/ast/node.rb +330 -0
  64. data/lib/plume/ast/noop_column_constraint.rb +15 -0
  65. data/lib/plume/ast/noop_table_constraint.rb +15 -0
  66. data/lib/plume/ast/not_like_expression.rb +7 -0
  67. data/lib/plume/ast/not_null_column_constraint.rb +17 -0
  68. data/lib/plume/ast/null_column_constraint.rb +17 -0
  69. data/lib/plume/ast/ordering_term.rb +32 -0
  70. data/lib/plume/ast/over_clause.rb +25 -0
  71. data/lib/plume/ast/parenthetical_expression.rb +37 -0
  72. data/lib/plume/ast/parenthetical_source.rb +19 -0
  73. data/lib/plume/ast/pragma_statement.rb +26 -0
  74. data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
  75. data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
  76. data/lib/plume/ast/raise_expression.rb +23 -0
  77. data/lib/plume/ast/reindex_statement.rb +15 -0
  78. data/lib/plume/ast/release_statement.rb +16 -0
  79. data/lib/plume/ast/result_column.rb +18 -0
  80. data/lib/plume/ast/returning_clause.rb +12 -0
  81. data/lib/plume/ast/rollback_statement.rb +19 -0
  82. data/lib/plume/ast/savepoint_statement.rb +16 -0
  83. data/lib/plume/ast/select_core.rb +27 -0
  84. data/lib/plume/ast/select_statement.rb +34 -0
  85. data/lib/plume/ast/star_function_argument.rb +6 -0
  86. data/lib/plume/ast/star_result_column.rb +16 -0
  87. data/lib/plume/ast/strict_table_option.rb +6 -0
  88. data/lib/plume/ast/subquery_expression.rb +14 -0
  89. data/lib/plume/ast/subquery_source.rb +19 -0
  90. data/lib/plume/ast/table_constraint.rb +7 -0
  91. data/lib/plume/ast/table_function.rb +28 -0
  92. data/lib/plume/ast/table_name.rb +28 -0
  93. data/lib/plume/ast/table_option.rb +7 -0
  94. data/lib/plume/ast/ternary_expression.rb +26 -0
  95. data/lib/plume/ast/token.rb +183 -0
  96. data/lib/plume/ast/unary_expression.rb +36 -0
  97. data/lib/plume/ast/unique_column_constraint.rb +17 -0
  98. data/lib/plume/ast/unique_table_constraint.rb +20 -0
  99. data/lib/plume/ast/update_statement.rb +35 -0
  100. data/lib/plume/ast/upsert_clause.rb +26 -0
  101. data/lib/plume/ast/vacuum_statement.rb +18 -0
  102. data/lib/plume/ast/values_clause.rb +12 -0
  103. data/lib/plume/ast/values_row.rb +13 -0
  104. data/lib/plume/ast/variable.rb +15 -0
  105. data/lib/plume/ast/window_definition.rb +26 -0
  106. data/lib/plume/ast/with_clause.rb +15 -0
  107. data/lib/plume/ast/without_rowid_table_option.rb +6 -0
  108. data/lib/plume/catalog.rb +276 -0
  109. data/lib/plume/comment.rb +73 -0
  110. data/lib/plume/dump.rb +143 -0
  111. data/lib/plume/fingerprint.rb +86 -0
  112. data/lib/plume/lexer.rb +640 -0
  113. data/lib/plume/literal_patches.rb +76 -0
  114. data/lib/plume/normalize.rb +65 -0
  115. data/lib/plume/parameters.rb +52 -0
  116. data/lib/plume/parser.rb +4064 -0
  117. data/lib/plume/pretty_printer.rb +202 -0
  118. data/lib/plume/printer.rb +525 -0
  119. data/lib/plume/sqlite_grammar.rb +86 -0
  120. data/lib/plume/truncate.rb +67 -0
  121. data/lib/plume/union.rb +28 -0
  122. data/lib/plume/version.rb +1 -1
  123. data/lib/plume.rb +274 -3
  124. metadata +176 -12
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plume
4
+ # Constants turned into `?` parameters, so the same query with different values comes out the
5
+ # same - for grouping a log the way pg_stat_statements does, or for keeping values out of it.
6
+ # Numbers, strings and blobs are replaced, a sign in front of a number with them. NULL, TRUE,
7
+ # FALSE and CURRENT_* are not, since `IS ?` would not mean `IS NULL`. Neither is an integer
8
+ # standing alone in ORDER BY or GROUP BY, which SQLite takes for a column's position.
9
+ # Only SELECT, INSERT, UPDATE and DELETE (and EXPLAIN of them) are normalized, other statements
10
+ # are left as they were: their constants are schema, not data
11
+ module Normalize
12
+ extend self
13
+
14
+ STATEMENTS = [SelectStatement, InsertStatement, UpdateStatement, DeleteStatement].freeze
15
+
16
+ # The SQL with each constant swapped for `?` in place, everything else - spacing, case,
17
+ # comments - kept as written
18
+ def text(sql)
19
+ src = sql.b
20
+ spans = Parser.new(sql).parse.flat_map { |statement| constants(statement).map(&:location) }
21
+ out = +""
22
+ at = 0
23
+ spans.sort_by(&:first).each do |beg, fin|
24
+ out << src.byteslice(at, beg - at) << "?"
25
+ at = fin
26
+ end
27
+ out << src.byteslice(at, src.bytesize - at)
28
+ out.force_encoding(sql.encoding)
29
+ end
30
+
31
+ # A copy of the tree with each constant replaced by a `?` Variable
32
+ def tree(node)
33
+ targets = Set.new(constants(node)).compare_by_identity
34
+ node.transform { |n| targets.include?(n) ? Variable.new(name: "?") : n }
35
+ end
36
+
37
+ # The nodes to replace: literals, or a sign with the literal under it
38
+ def constants(statement)
39
+ root = (ExplainStatement === statement) ? statement.statement : statement
40
+ return [] unless STATEMENTS.any? { |klass| klass === root }
41
+
42
+ found = []
43
+ # By identity: hashing a node hashes its whole subtree
44
+ positional = Set.new.compare_by_identity
45
+ root.walk_with_slots do |node, parent, prop|
46
+ case node
47
+ when OrderingTerm
48
+ positional << node if SelectStatement === parent && prop == :orderings
49
+ when UnaryExpression
50
+ found << node if (node.operator in :NEGATE | :IDENTITY) && value?(node.operand)
51
+ when LiteralExpression
52
+ next if UnaryExpression === parent && (parent.operator in :NEGATE | :IDENTITY)
53
+ next if Integer === node.value && (positional.include?(parent) || (SelectCore === parent && prop == :groupings))
54
+
55
+ found << node if value?(node)
56
+ end
57
+ end
58
+ found
59
+ end
60
+
61
+ private
62
+
63
+ def value?(node) = LiteralExpression === node && (node.value in Numeric | String)
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plume
4
+ # The bind parameters of one statement, numbered the way SQLite numbers them: `?` takes the
5
+ # next number, `?N` takes N, and a name keeps the number it first got. `count` and `names`
6
+ # are what `sqlite3_bind_parameter_count` and `sqlite3_bind_parameter_name` would answer
7
+ class Parameters
8
+ include Enumerable
9
+
10
+ # One place a parameter appears. `name` is nil for a bare `?`
11
+ Parameter = Data.define(:number, :name, :node)
12
+
13
+ attr_reader :count, :names
14
+
15
+ def initialize(statement)
16
+ slots = {}
17
+ @count = 0
18
+ @parameters = variables(statement).map do |variable|
19
+ name = variable.name
20
+ number =
21
+ if name == "?"
22
+ @count += 1
23
+ elsif (n = name[/\A\?(\d+)\z/, 1]&.to_i)
24
+ slots[n] ||= name
25
+ @count = n if n > @count
26
+ n
27
+ elsif (n = slots.key(name))
28
+ n
29
+ else
30
+ slots[@count += 1] = name
31
+ @count
32
+ end
33
+ Parameter.new(number:, name: (name == "?") ? nil : name, node: variable)
34
+ end.freeze
35
+ @names = Array.new(@count) { |i| slots[i + 1] }.freeze
36
+ end
37
+
38
+ def each(&) = @parameters.each(&)
39
+
40
+ def size = @parameters.size
41
+
42
+ # The number a name was given, as `sqlite3_bind_parameter_index` answers it
43
+ def index(name) = @names.index(name)&.+(1)
44
+
45
+ private
46
+
47
+ # SQLite numbers them as it reads them, which is not always the order of the tree
48
+ def variables(statement)
49
+ statement.each_node.grep(Variable).each_with_index.sort_by { |v, i| [v.location&.first || 0, i] }.map(&:first)
50
+ end
51
+ end
52
+ end