predicate 2.3.3 → 2.7.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 (88) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +4 -0
  3. data/LICENSE.md +17 -19
  4. data/README.md +467 -0
  5. data/bin/g +2 -0
  6. data/lib/predicate/dsl.rb +138 -0
  7. data/lib/predicate/factory.rb +149 -40
  8. data/lib/predicate/grammar.rb +11 -2
  9. data/lib/predicate/grammar.sexp.yml +31 -0
  10. data/lib/predicate/nodes/${op_name}.rb.jeny +12 -0
  11. data/lib/predicate/nodes/and.rb +9 -0
  12. data/lib/predicate/nodes/binary_func.rb +20 -0
  13. data/lib/predicate/nodes/contradiction.rb +2 -7
  14. data/lib/predicate/nodes/dyadic_comp.rb +1 -3
  15. data/lib/predicate/nodes/empty.rb +14 -0
  16. data/lib/predicate/nodes/eq.rb +11 -3
  17. data/lib/predicate/nodes/expr.rb +5 -3
  18. data/lib/predicate/nodes/has_size.rb +14 -0
  19. data/lib/predicate/nodes/identifier.rb +1 -3
  20. data/lib/predicate/nodes/in.rb +7 -6
  21. data/lib/predicate/nodes/intersect.rb +3 -23
  22. data/lib/predicate/nodes/literal.rb +1 -3
  23. data/lib/predicate/nodes/match.rb +1 -21
  24. data/lib/predicate/nodes/nadic_bool.rb +1 -3
  25. data/lib/predicate/nodes/native.rb +1 -3
  26. data/lib/predicate/nodes/not.rb +1 -3
  27. data/lib/predicate/nodes/opaque.rb +1 -3
  28. data/lib/predicate/nodes/qualified_identifier.rb +1 -3
  29. data/lib/predicate/nodes/set_op.rb +26 -0
  30. data/lib/predicate/nodes/subset.rb +11 -0
  31. data/lib/predicate/nodes/superset.rb +11 -0
  32. data/lib/predicate/nodes/tautology.rb +6 -7
  33. data/lib/predicate/nodes/unary_func.rb +16 -0
  34. data/lib/predicate/nodes/var.rb +46 -0
  35. data/lib/predicate/postgres/ext/factory.rb +28 -0
  36. data/lib/predicate/postgres/ext/to_sequel.rb +26 -0
  37. data/lib/predicate/postgres/ext.rb +2 -0
  38. data/lib/predicate/postgres/pg_array/empty.rb +10 -0
  39. data/lib/predicate/postgres/pg_array/literal.rb +10 -0
  40. data/lib/predicate/postgres/pg_array/overlaps.rb +10 -0
  41. data/lib/predicate/postgres/pg_array.rb +25 -0
  42. data/lib/predicate/postgres/rewriter.rb +49 -0
  43. data/lib/predicate/postgres.rb +4 -0
  44. data/lib/predicate/processors/qualifier.rb +4 -0
  45. data/lib/predicate/processors/renamer.rb +4 -0
  46. data/lib/predicate/processors/to_s.rb +28 -0
  47. data/lib/predicate/processors/unqualifier.rb +4 -0
  48. data/lib/predicate/sequel/to_sequel.rb +5 -1
  49. data/lib/predicate/sugar.rb +47 -0
  50. data/lib/predicate/version.rb +2 -2
  51. data/lib/predicate.rb +28 -4
  52. data/spec/dsl/test_dsl.rb +204 -0
  53. data/spec/dsl/test_evaluate.rb +65 -0
  54. data/spec/dsl/test_respond_to_missing.rb +35 -0
  55. data/spec/dsl/test_to_skake_case.rb +38 -0
  56. data/spec/factory/shared/a_comparison_factory_method.rb +1 -0
  57. data/spec/factory/test_${op_name}.rb.jeny +12 -0
  58. data/spec/factory/test_comp.rb +28 -5
  59. data/spec/factory/test_empty.rb +11 -0
  60. data/spec/factory/test_has_size.rb +11 -0
  61. data/spec/factory/test_match.rb +1 -0
  62. data/spec/factory/test_set_ops.rb +18 -0
  63. data/spec/factory/test_var.rb +22 -0
  64. data/spec/factory/test_vars.rb +27 -0
  65. data/spec/nodes/${op_name}.jeny/test_evaluate.rb.jeny +19 -0
  66. data/spec/nodes/empty/test_evaluate.rb +42 -0
  67. data/spec/nodes/has_size/test_evaluate.rb +44 -0
  68. data/spec/postgres/test_factory.rb +48 -0
  69. data/spec/postgres/test_to_postgres.rb +41 -0
  70. data/spec/postgres/test_to_sequel.rb +44 -0
  71. data/spec/predicate/test_and_split.rb +18 -0
  72. data/spec/predicate/test_attr_split.rb +18 -0
  73. data/spec/predicate/test_constant_variables.rb +24 -2
  74. data/spec/predicate/test_constants.rb +24 -0
  75. data/spec/predicate/test_evaluate.rb +205 -3
  76. data/spec/predicate/test_free_variables.rb +1 -1
  77. data/spec/predicate/test_to_hash.rb +40 -0
  78. data/spec/predicate/test_to_s.rb +37 -0
  79. data/spec/sequel/test_to_sequel.rb +26 -10
  80. data/spec/shared/a_predicate.rb +34 -0
  81. data/spec/spec_helper.rb +18 -1
  82. data/spec/test_predicate.rb +78 -33
  83. data/spec/test_readme.rb +80 -0
  84. data/spec/test_sugar.rb +48 -0
  85. data/tasks/test.rake +3 -3
  86. metadata +67 -11
  87. data/spec/factory/test_between.rb +0 -12
  88. data/spec/factory/test_intersect.rb +0 -12
@@ -2,9 +2,7 @@ class Predicate
2
2
  module Opaque
3
3
  include Expr
4
4
 
5
- def priority
6
- 100
7
- end
5
+ def priority; 100; end
8
6
 
9
7
  def free_variables
10
8
  @free_variables ||= []
@@ -2,9 +2,7 @@ class Predicate
2
2
  module QualifiedIdentifier
3
3
  include Expr
4
4
 
5
- def priority
6
- 100
7
- end
5
+ def priority; 100; end
8
6
 
9
7
  def qualifier
10
8
  self[1]
@@ -0,0 +1,26 @@
1
+ class Predicate
2
+ module SetOp
3
+ include Expr
4
+
5
+ def priority; 80; end
6
+
7
+ def left
8
+ self[1]
9
+ end
10
+ alias :identifier :left
11
+
12
+ def right
13
+ self[2]
14
+ end
15
+ alias :values :right
16
+
17
+ def free_variables
18
+ @free_variables ||= left.free_variables | right.free_variables
19
+ end
20
+
21
+ def constant_variables
22
+ []
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ class Predicate
2
+ module Subset
3
+ include SetOp
4
+
5
+ def evaluate(tuple)
6
+ x, y = left.evaluate(tuple), right.evaluate(tuple)
7
+ x && y && (x & y == x)
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Predicate
2
+ module Superset
3
+ include SetOp
4
+
5
+ def evaluate(tuple)
6
+ x, y = left.evaluate(tuple), right.evaluate(tuple)
7
+ x && y && (x & y == y)
8
+ end
9
+
10
+ end
11
+ end
@@ -18,13 +18,8 @@ class Predicate
18
18
  self
19
19
  end
20
20
 
21
- def dyadic_priority
22
- 1000
23
- end
24
-
25
- def priority
26
- 100
27
- end
21
+ def dyadic_priority; 1000; end
22
+ def priority; 100; end
28
23
 
29
24
  def free_variables
30
25
  @free_variables ||= []
@@ -38,5 +33,9 @@ class Predicate
38
33
  true
39
34
  end
40
35
 
36
+ def to_hash
37
+ {}
38
+ end
39
+
41
40
  end
42
41
  end
@@ -0,0 +1,16 @@
1
+ class Predicate
2
+ module UnaryFunc
3
+ include Expr
4
+
5
+ def priority; 80; end
6
+
7
+ def operand
8
+ self[1]
9
+ end
10
+
11
+ def free_variables
12
+ @free_variables ||= operand.free_variables
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ class Predicate
2
+ module Var
3
+ include Expr
4
+
5
+ def priority; 100; end
6
+
7
+ def formaldef
8
+ self[1]
9
+ end
10
+
11
+ def semantics
12
+ self[2]
13
+ end
14
+
15
+ def free_variables
16
+ @free_variables ||= [ [formaldef, semantics] ]
17
+ end
18
+
19
+ def dig_terms
20
+ @dig_terms ||= case formaldef
21
+ when String
22
+ formaldef.split(".").map{|elm|
23
+ elm =~ /^\d+$/ ? elm.to_i : elm.to_sym
24
+ }
25
+ when Array
26
+ formaldef
27
+ else
28
+ raise ArgumentError, "Unrecognized variable def `#{formaldef}`"
29
+ end
30
+ end
31
+
32
+ def evaluate(on)
33
+ case semantics
34
+ when :dig
35
+ dig_terms.inject(on){|ctx,elm| ctx.dig(elm) }
36
+ when :send
37
+ dig_terms.inject(on){|ctx,elm| ctx.__send__(elm.to_sym) }
38
+ when :public_send
39
+ dig_terms.inject(on){|ctx,elm| ctx.public_send(elm.to_sym) }
40
+ else
41
+ raise ArgumentError, "Unrecognized variable semantics `#{semantics}`"
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ class Predicate
2
+ module Factory
3
+ def pg_array_literal(value, type = :varchar)
4
+ _factor_predicate([
5
+ :pg_array_literal,
6
+ value,
7
+ type
8
+ ], Postgres::PgArray::Literal)
9
+ end
10
+
11
+ def pg_array_overlaps(left, right, type = :varchar)
12
+ _factor_predicate([
13
+ :pg_array_overlaps,
14
+ sexpr(left),
15
+ sexpr(right),
16
+ type
17
+ ], Postgres::PgArray::Overlaps)
18
+ end
19
+
20
+ def pg_array_empty(operand, type = :varchar)
21
+ _factor_predicate([
22
+ :pg_empty,
23
+ sexpr(operand),
24
+ type
25
+ ], Postgres::PgArray::Empty)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ class Predicate
2
+ module Postgres
3
+ module ToSequel
4
+ def on_pg_array_literal(sexpr)
5
+ PgArray.to_pg_array(sexpr[1], sexpr[2])
6
+ end
7
+
8
+ def on_pg_array_overlaps(sexpr)
9
+ type = sexpr.last
10
+ l = PgArray.to_pg_array(apply(sexpr.left), type)
11
+ r = PgArray.to_pg_array(apply(sexpr.right), type)
12
+ l.overlaps(r)
13
+ end
14
+
15
+ def on_pg_array_empty(sexpr)
16
+ left = PgArray.to_pg_array(apply(sexpr.operand))
17
+ right = PgArray.to_pg_array([], sexpr.last)
18
+ ::Sequel.expr(left => right)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class Predicate::ToSequel
25
+ include Predicate::Postgres::ToSequel
26
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'ext/factory'
2
+ require_relative 'ext/to_sequel'
@@ -0,0 +1,10 @@
1
+ class Predicate
2
+ module Postgres
3
+ module PgArray
4
+ module Empty
5
+ include Predicate::UnaryFunc
6
+
7
+ end # module Empty
8
+ end # module PgArray
9
+ end # module Postgres
10
+ end # class Predicate
@@ -0,0 +1,10 @@
1
+ class Predicate
2
+ module Postgres
3
+ module PgArray
4
+ module Literal
5
+ include Predicate::Literal
6
+
7
+ end # module Literal
8
+ end # module PgArray
9
+ end # module Postgres
10
+ end # class Predicate
@@ -0,0 +1,10 @@
1
+ class Predicate
2
+ module Postgres
3
+ module PgArray
4
+ module Overlaps
5
+ include Predicate::BinaryFunc
6
+
7
+ end # module Overlaps
8
+ end # module PgArray
9
+ end # module Postgres
10
+ end # class Predicate
@@ -0,0 +1,25 @@
1
+ class Predicate
2
+ module Postgres
3
+ module PgArray
4
+
5
+ def to_pg_array(arg, type = :varchar)
6
+ if arg.is_a?(Sequel::Postgres::PGArray)
7
+ arg
8
+ elsif arg.is_a?(Sequel::SQL::Wrapper)
9
+ ::Sequel.pg_array(arg.value, type)
10
+ elsif arg.is_a?(Array)
11
+ ::Sequel.pg_array(arg, type)
12
+ elsif arg.respond_to?(:pg_array)
13
+ arg.pg_array
14
+ else
15
+ raise NotSupportedError, "Unexpected pg_array arg `#{arg}`::`#{arg.class}`"
16
+ end
17
+ end
18
+ module_function :to_pg_array
19
+
20
+ end # module PgArray
21
+ end # module Postgres
22
+ end # class Predicate
23
+ require_relative 'pg_array/empty'
24
+ require_relative 'pg_array/overlaps'
25
+ require_relative 'pg_array/literal'
@@ -0,0 +1,49 @@
1
+ class Predicate
2
+ module Postgres
3
+ class Rewriter < Sexpr::Rewriter
4
+ grammar Grammar
5
+
6
+ class ToLiteral < Sexpr::Rewriter
7
+ grammar Grammar
8
+
9
+ def on_literal(sexpr)
10
+ if sexpr.last.is_a?(Array)
11
+ [ :pg_array_literal, sexpr.last, :varchar ]
12
+ else
13
+ sexpr
14
+ end
15
+ end
16
+
17
+ alias :on_missing :copy_and_apply
18
+ end
19
+
20
+ def on_intersect(sexpr)
21
+ rewriter = ToLiteral.new
22
+ rewritten = sexpr[1..-1]
23
+ .map{|expr| rewriter.call(expr) }
24
+ .unshift(:pg_array_overlaps)
25
+ rewritten.extend(PgArray::Overlaps)
26
+ end
27
+
28
+ def on_empty(sexpr)
29
+ rewritten = sexpr[1..-1]
30
+ .map{|expr| apply(expr) }
31
+ .unshift(:pg_array_empty)
32
+ .push(:varchar)
33
+ rewritten.extend(PgArray::Empty)
34
+ end
35
+
36
+ alias :on_missing :copy_and_apply
37
+ end
38
+ end
39
+
40
+ module Expr
41
+ def to_postgres(*args)
42
+ Postgres::Rewriter.new(*args).call(self)
43
+ end
44
+ end
45
+
46
+ def to_postgres(*args)
47
+ Predicate.new(expr.to_postgres(*args))
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'sequel'
2
+ require_relative 'postgres/pg_array'
3
+ require_relative 'postgres/rewriter'
4
+ require_relative 'postgres/ext'
@@ -22,6 +22,10 @@ class Predicate
22
22
  raise NotSupportedError
23
23
  end
24
24
 
25
+ def on_var(sexpr)
26
+ raise NotSupportedError
27
+ end
28
+
25
29
  alias :on_missing :copy_and_apply
26
30
 
27
31
  end
@@ -19,6 +19,10 @@ class Predicate
19
19
  raise NotSupportedError
20
20
  end
21
21
 
22
+ def on_var(sexpr)
23
+ raise NotSupportedError
24
+ end
25
+
22
26
  alias :on_missing :copy_and_apply
23
27
 
24
28
  end
@@ -57,6 +57,14 @@ class Predicate
57
57
  "#{apply(sexpr.identifier)} INTERSECTS #{to_literal(sexpr.values)}"
58
58
  end
59
59
 
60
+ def on_subset(sexpr)
61
+ "#{apply(sexpr.identifier)} IS SUBSET OF #{to_literal(sexpr.values)}"
62
+ end
63
+
64
+ def on_superset(sexpr)
65
+ "#{apply(sexpr.identifier)} IS SUPERSET OF #{to_literal(sexpr.values)}"
66
+ end
67
+
60
68
  def on_literal(sexpr)
61
69
  to_literal(sexpr.last)
62
70
  end
@@ -69,16 +77,36 @@ class Predicate
69
77
  "#{apply(sexpr.left)} =~ #{apply(sexpr.right)}"
70
78
  end
71
79
 
80
+ def on_empty(sexpr)
81
+ "EMPTY(#{commalist(sexpr.body)})"
82
+ end
83
+
84
+ def on_size(sexpr)
85
+ "SIZE(#{commalist(sexpr.body)})"
86
+ end
87
+
88
+ #jeny(predicate) def on_${op_name}(sexpr)
89
+ #jeny(predicate) "${OP_NAME}(#{commalist(sexpr.body)})"
90
+ #jeny(predicate) end
91
+
72
92
  def on_native(sexpr)
73
93
  sexpr.last.inspect
74
94
  end
75
95
 
96
+ def on_var(sexpr)
97
+ "#{sexpr.semantics}(#{sexpr.formaldef})"
98
+ end
99
+
76
100
  def on_missing(sexpr)
77
101
  raise "Unimplemented: #{sexpr.first}"
78
102
  end
79
103
 
80
104
  protected
81
105
 
106
+ def commalist(of)
107
+ of.map{|o| apply(o) }.join(',')
108
+ end
109
+
82
110
  def to_literal(x)
83
111
  case x
84
112
  when Placeholder then "$#{x.object_id}"
@@ -11,6 +11,10 @@ class Predicate
11
11
  raise NotSupportedError
12
12
  end
13
13
 
14
+ def on_var(sexpr)
15
+ raise NotSupportedError
16
+ end
17
+
14
18
  alias :on_missing :copy_and_apply
15
19
 
16
20
  end
@@ -106,14 +106,18 @@ class Predicate
106
106
 
107
107
  def on_opaque(sexpr)
108
108
  return [sexpr.last] if sexpr.last.respond_to?(:sql_literal)
109
+ return [sexpr.last] if sexpr.last.respond_to?(:sql)
109
110
  raise Error, "Unable to compile #{sexpr} to Sequel"
110
111
  end
111
112
 
112
113
  def on_unsupported(sexpr)
113
- raise NotSupportedError
114
+ raise NotSupportedError, "Unsupported predicate #{sexpr}"
114
115
  end
116
+ alias :on_var :on_unsupported
115
117
  alias :on_native :on_unsupported
116
118
  alias :on_intersect :on_unsupported
119
+ alias :on_subset :on_unsupported
120
+ alias :on_superset :on_unsupported
117
121
  end
118
122
  include Methods
119
123
  end # class ToSequel
@@ -0,0 +1,47 @@
1
+ class Predicate
2
+ module Sugar
3
+
4
+ [ :eq, :neq, :lt, :lte, :gt, :gte ].each do |m|
5
+ define_method(m) do |left, right=nil|
6
+ return comp(m, left) if TupleLike===left && right.nil?
7
+ super(left, right)
8
+ end
9
+ end
10
+
11
+ def between(middle, lower_bound, upper_bound)
12
+ _factor_predicate [:and, [:gte, sexpr(middle), sexpr(lower_bound)],
13
+ [:lte, sexpr(middle), sexpr(upper_bound)]]
14
+ end
15
+
16
+ def match(left, right, options = nil)
17
+ super(left, right, options)
18
+ end
19
+
20
+ def min_size(left, right)
21
+ unless right.is_a?(Integer)
22
+ raise ArgumentError, "Integer expected, got #{right}"
23
+ end
24
+ if RUBY_VERSION >= "2.6"
25
+ has_size(left, Range.new(right,nil))
26
+ else
27
+ has_size(left, Range.new(right,(2**32-1)))
28
+ end
29
+ end
30
+
31
+ def max_size(left, right)
32
+ unless right.is_a?(Integer)
33
+ raise ArgumentError, "Integer expected, got #{right}"
34
+ end
35
+ has_size(left, 0..right)
36
+ end
37
+
38
+ def is_null(operand)
39
+ eq(operand, nil)
40
+ end
41
+
42
+ #jeny(sugar) def ${op_name}(*args)
43
+ #jeny(sugar) TODO
44
+ #jeny(sugar) end
45
+
46
+ end # module Sugar
47
+ end # class Predicate
@@ -1,8 +1,8 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 2
4
- MINOR = 3
5
- TINY = 3
4
+ MINOR = 7
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/lib/predicate.rb CHANGED
@@ -2,12 +2,16 @@ require 'sexpr'
2
2
  require_relative 'predicate/version'
3
3
  require_relative 'predicate/placeholder'
4
4
  require_relative 'predicate/factory'
5
+ require_relative 'predicate/sugar'
5
6
  require_relative 'predicate/grammar'
6
7
  require_relative 'predicate/processors'
8
+ require_relative 'predicate/dsl'
7
9
  class Predicate
8
10
 
9
- class NotSupportedError < StandardError; end
10
- class UnboundError < StandardError; end
11
+ class Error < StandardError; end
12
+ class NotSupportedError < Error; end
13
+ class UnboundError < Error; end
14
+ class TypeError < Error; end
11
15
 
12
16
  TupleLike = ->(t){ t.is_a?(Hash) }
13
17
 
@@ -21,6 +25,7 @@ class Predicate
21
25
 
22
26
  class << self
23
27
  include Factory
28
+ include Sugar
24
29
 
25
30
  def coerce(arg)
26
31
  case arg
@@ -36,10 +41,22 @@ class Predicate
36
41
  end
37
42
  alias :parse :coerce
38
43
 
44
+ def dsl(var = var(".", :dig), &bl)
45
+ Predicate::Dsl.new(var, false).instance_eval(&bl)
46
+ end
47
+
48
+ def currying(var = var(".", :dig), &bl)
49
+ Predicate::Dsl.new(var, true).instance_eval(&bl)
50
+ end
51
+
39
52
  private
40
53
 
41
- def _factor_predicate(arg)
42
- Predicate.new Grammar.sexpr(arg)
54
+ def _factor_predicate(arg, *mods)
55
+ expr = Grammar.sexpr(arg)
56
+ mods.each do |mod|
57
+ expr.extend(mod)
58
+ end
59
+ Predicate.new(expr)
43
60
  end
44
61
 
45
62
  end
@@ -142,4 +159,11 @@ class Predicate
142
159
  expr.to_s(scope)
143
160
  end
144
161
 
162
+ # If possible, converts this predicate back to a `{ attr: value, ... }`
163
+ # hash. Raises an IllegalArgumentError if the predicate cannot be
164
+ # represented that way.
165
+ def to_hash
166
+ expr.to_hash
167
+ end
168
+
145
169
  end # class Predicate