rasti-db 2.0.0 → 2.3.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -3
  3. data/lib/rasti/db.rb +11 -1
  4. data/lib/rasti/db/collection.rb +10 -1
  5. data/lib/rasti/db/computed_attribute.rb +22 -0
  6. data/lib/rasti/db/nql/filter_condition_strategies/base.rb +17 -0
  7. data/lib/rasti/db/nql/filter_condition_strategies/postgres.rb +21 -0
  8. data/lib/rasti/db/nql/filter_condition_strategies/sqlite.rb +21 -0
  9. data/lib/rasti/db/nql/filter_condition_strategies/types/generic.rb +49 -0
  10. data/lib/rasti/db/nql/filter_condition_strategies/types/pg_array.rb +32 -0
  11. data/lib/rasti/db/nql/filter_condition_strategies/types/sqlite_array.rb +34 -0
  12. data/lib/rasti/db/nql/filter_condition_strategies/unsupported_type_comparison.rb +22 -0
  13. data/lib/rasti/db/nql/nodes/array_content.rb +21 -0
  14. data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
  15. data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
  16. data/lib/rasti/db/nql/nodes/comparisons/base.rb +15 -1
  17. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +0 -4
  18. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +0 -4
  19. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +0 -4
  20. data/lib/rasti/db/nql/nodes/comparisons/include.rb +0 -4
  21. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +0 -4
  22. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +0 -4
  23. data/lib/rasti/db/nql/nodes/comparisons/like.rb +0 -4
  24. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +0 -4
  25. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +0 -4
  26. data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
  27. data/lib/rasti/db/nql/nodes/constants/array.rb +17 -0
  28. data/lib/rasti/db/nql/nodes/constants/base.rb +17 -0
  29. data/lib/rasti/db/nql/nodes/constants/false.rb +1 -1
  30. data/lib/rasti/db/nql/nodes/constants/float.rb +1 -1
  31. data/lib/rasti/db/nql/nodes/constants/integer.rb +1 -1
  32. data/lib/rasti/db/nql/nodes/constants/literal_string.rb +1 -1
  33. data/lib/rasti/db/nql/nodes/constants/string.rb +1 -1
  34. data/lib/rasti/db/nql/nodes/constants/time.rb +1 -1
  35. data/lib/rasti/db/nql/nodes/constants/true.rb +1 -1
  36. data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
  37. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
  38. data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
  39. data/lib/rasti/db/nql/syntax.rb +262 -44
  40. data/lib/rasti/db/nql/syntax.treetop +27 -14
  41. data/lib/rasti/db/query.rb +42 -14
  42. data/lib/rasti/db/type_converters/postgres.rb +32 -36
  43. data/lib/rasti/db/type_converters/postgres_types/array.rb +11 -9
  44. data/lib/rasti/db/type_converters/postgres_types/hstore.rb +10 -9
  45. data/lib/rasti/db/type_converters/postgres_types/json.rb +17 -14
  46. data/lib/rasti/db/type_converters/postgres_types/jsonb.rb +17 -14
  47. data/lib/rasti/db/type_converters/sqlite.rb +62 -0
  48. data/lib/rasti/db/type_converters/sqlite_types/array.rb +34 -0
  49. data/lib/rasti/db/version.rb +1 -1
  50. data/rasti-db.gemspec +1 -0
  51. data/spec/collection_spec.rb +19 -11
  52. data/spec/computed_attribute_spec.rb +32 -0
  53. data/spec/minitest_helper.rb +32 -5
  54. data/spec/model_spec.rb +1 -1
  55. data/spec/nql/computed_attributes_spec.rb +29 -0
  56. data/spec/nql/filter_condition_spec.rb +23 -4
  57. data/spec/nql/filter_condition_strategies_spec.rb +112 -0
  58. data/spec/nql/syntax_parser_spec.rb +36 -5
  59. data/spec/query_spec.rb +254 -39
  60. data/spec/type_converters/sqlite_spec.rb +66 -0
  61. metadata +38 -3
  62. data/lib/rasti/db/nql/nodes/field.rb +0 -23
@@ -42,44 +42,49 @@ module Rasti
42
42
  comparison_equal
43
43
  end
44
44
 
45
- rule field
46
- _tables:(table:field_name '.')* _column:field_name <Nodes::Field>
45
+ rule attribute
46
+ _tables:(table:attribute_name '.')* _column:attribute_name <Nodes::Attribute>
47
47
  end
48
48
 
49
49
  rule comparison_include
50
- field:field space* comparator:':' space* argument:basic <Nodes::Comparisons::Include>
50
+ attribute:attribute space* comparator:':' space* argument:argument <Nodes::Comparisons::Include>
51
51
  end
52
52
 
53
53
  rule comparison_not_include
54
- field:field space* comparator:'!:' space* argument:basic <Nodes::Comparisons::NotInclude>
54
+ attribute:attribute space* comparator:'!:' space* argument:argument <Nodes::Comparisons::NotInclude>
55
55
  end
56
56
 
57
57
  rule comparison_like
58
- field:field space* comparator:'~' space* argument:basic <Nodes::Comparisons::Like>
58
+ attribute:attribute space* comparator:'~' space* argument:argument <Nodes::Comparisons::Like>
59
59
  end
60
60
 
61
61
  rule comparison_greater_than
62
- field:field space* comparator:'>' space* argument:basic <Nodes::Comparisons::GreaterThan>
62
+ attribute:attribute space* comparator:'>' space* argument:argument <Nodes::Comparisons::GreaterThan>
63
63
  end
64
64
 
65
65
  rule comparison_greater_than_or_equal
66
- field:field space* comparator:'>=' space* argument:basic <Nodes::Comparisons::GreaterThanOrEqual>
66
+ attribute:attribute space* comparator:'>=' space* argument:argument <Nodes::Comparisons::GreaterThanOrEqual>
67
67
  end
68
68
 
69
69
  rule comparison_less_than
70
- field:field space* comparator:'<' space* argument:basic <Nodes::Comparisons::LessThan>
70
+ attribute:attribute space* comparator:'<' space* argument:argument <Nodes::Comparisons::LessThan>
71
71
  end
72
72
 
73
73
  rule comparison_less_than_or_equal
74
- field:field space* comparator:'<=' space* argument:basic <Nodes::Comparisons::LessThanOrEqual>
74
+ attribute:attribute space* comparator:'<=' space* argument:argument <Nodes::Comparisons::LessThanOrEqual>
75
75
  end
76
76
 
77
77
  rule comparison_not_equal
78
- field:field space* comparator:'!=' space* argument:basic <Nodes::Comparisons::NotEqual>
78
+ attribute:attribute space* comparator:'!=' space* argument:argument <Nodes::Comparisons::NotEqual>
79
79
  end
80
80
 
81
81
  rule comparison_equal
82
- field:field space* comparator:'=' space* argument:basic <Nodes::Comparisons::Equal>
82
+ attribute:attribute space* comparator:'=' space* argument:argument <Nodes::Comparisons::Equal>
83
+ end
84
+
85
+ rule argument
86
+ array /
87
+ basic
83
88
  end
84
89
 
85
90
  rule basic
@@ -91,11 +96,19 @@ module Rasti
91
96
  string
92
97
  end
93
98
 
99
+ rule array
100
+ open:'[' space* contents:(array_content / basic) space* close:']' <Nodes::Constants::Array>
101
+ end
102
+
103
+ rule array_content
104
+ left:basic space* ',' space* right:(array_content / basic) <Nodes::ArrayContent>
105
+ end
106
+
94
107
  rule space
95
108
  [\s\t\n]
96
109
  end
97
110
 
98
- rule field_name
111
+ rule attribute_name
99
112
  [a-z_]+
100
113
  end
101
114
 
@@ -130,7 +143,7 @@ module Rasti
130
143
  end
131
144
 
132
145
  rule valid_character
133
- [0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+-_'?!$*/\s]
146
+ [0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+\--Z\\^_'?!$*/\s]
134
147
  end
135
148
 
136
149
  rule boolean
@@ -159,7 +172,7 @@ module Rasti
159
172
  end
160
173
 
161
174
  rule reserved_character
162
- [&|.():!=<>~]
175
+ [&|.():!=<>~,\]\[]
163
176
  end
164
177
 
165
178
  end
@@ -9,7 +9,7 @@ module Rasti
9
9
  def initialize(environment:, collection_class:, dataset:, relations_graph:nil)
10
10
  @environment = environment
11
11
  @collection_class = collection_class
12
- @dataset = dataset
12
+ @dataset = dataset.qualify collection_class.collection_name
13
13
  @relations_graph = relations_graph || Relations::Graph.new(environment, collection_class)
14
14
  end
15
15
 
@@ -57,15 +57,36 @@ module Rasti
57
57
  build_query relations_graph: relations_graph.with_all_attributes_for(relations)
58
58
  end
59
59
 
60
+ def select_computed_attributes(*computed_attributes)
61
+ ds = computed_attributes.inject(dataset) do |ds, name|
62
+ computed_attribute = collection_class.computed_attributes[name]
63
+ computed_attribute.apply_join(ds).select_append(computed_attribute.identifier.as(name))
64
+ end
65
+ build_query dataset: ds
66
+ end
67
+
60
68
  def all
61
- with_graph(dataset.all).map do |row|
69
+ with_graph(dataset.all).map do |row|
62
70
  collection_class.model.new row
63
71
  end
64
72
  end
65
73
  alias_method :to_a, :all
66
74
 
67
- def each(&block)
68
- all.each(&block)
75
+ def each(batch_size:nil, &block)
76
+ if batch_size.nil?
77
+ all.each(&block)
78
+ else
79
+ each_batch(size: batch_size) do |models|
80
+ models.each { |model| block.call model }
81
+ end
82
+ end
83
+ end
84
+
85
+ def each_batch(size:, &block)
86
+ primary_keys.each_slice(size) do |pks|
87
+ query = where(collection_class.primary_key => pks)
88
+ block.call query.all
89
+ end
69
90
  end
70
91
 
71
92
  def graph(*relations)
@@ -74,11 +95,9 @@ module Rasti
74
95
 
75
96
  def join(*relations)
76
97
  graph = Relations::Graph.new environment, collection_class, relations
77
-
78
- ds = graph.add_joins(dataset)
79
- .distinct
80
- .select_all(collection_class.collection_name)
81
-
98
+
99
+ ds = graph.add_joins(dataset).distinct
100
+
82
101
  build_query dataset: ds
83
102
  end
84
103
 
@@ -96,12 +115,12 @@ module Rasti
96
115
 
97
116
  def first
98
117
  row = dataset.first
99
- row ? collection_class.model.new(with_graph(row)) : nil
118
+ row ? build_model(row) : nil
100
119
  end
101
120
 
102
121
  def last
103
122
  row = dataset.last
104
- row ? collection_class.model.new(with_graph(row)) : nil
123
+ row ? build_model(row) : nil
105
124
  end
106
125
 
107
126
  def detect(*args, &block)
@@ -118,10 +137,15 @@ module Rasti
118
137
 
119
138
  raise NQL::InvalidExpressionError.new(filter_expression) if sentence.nil?
120
139
 
140
+ ds = sentence.computed_attributes(collection_class).inject(dataset) do |ds, name|
141
+ collection_class.computed_attributes[name].apply_join ds
142
+ end
143
+ query = build_query dataset: ds
144
+
121
145
  dependency_tables = sentence.dependency_tables
122
- query = dependency_tables.empty? ? self : join(*dependency_tables)
123
-
124
- query.where sentence.filter_condition
146
+ query = query.join(*dependency_tables) unless dependency_tables.empty?
147
+
148
+ query.where sentence.filter_condition(collection_class)
125
149
  end
126
150
 
127
151
  private
@@ -139,6 +163,10 @@ module Rasti
139
163
  Query.new(**current_args.merge(args))
140
164
  end
141
165
 
166
+ def build_model(row)
167
+ collection_class.model.new with_graph(row)
168
+ end
169
+
142
170
  def chainable(&block)
143
171
  build_query dataset: instance_eval(&block)
144
172
  end
@@ -3,62 +3,58 @@ module Rasti
3
3
  module TypeConverters
4
4
  class Postgres
5
5
 
6
- CONVERTERS = [PostgresTypes::JSON, PostgresTypes::JSONB, PostgresTypes::HStore, PostgresTypes::Array]
7
-
8
- @to_db_mapping = {}
6
+ CONVERTERS = [
7
+ PostgresTypes::JSON,
8
+ PostgresTypes::JSONB,
9
+ PostgresTypes::HStore,
10
+ PostgresTypes::Array
11
+ ]
9
12
 
10
13
  class << self
11
14
 
12
15
  def to_db(db, collection_name, attribute_name, value)
13
- to_db_mapping = to_db_mapping_for db, collection_name
14
-
15
- if to_db_mapping.key? attribute_name
16
- to_db_mapping[attribute_name][:converter].to_db value, to_db_mapping[attribute_name][:sub_type]
17
- else
18
- value
19
- end
16
+ converter, type = find_to_db_converter_and_type db, collection_name, attribute_name
17
+ converter ? converter.to_db(value, type) : value
20
18
  end
21
19
 
22
- def from_db(object)
23
- if from_db_mapping.key? object.class
24
- from_db_mapping[object.class].from_db object
25
- else
26
- object
27
- end
20
+ def from_db(value)
21
+ converter = find_from_db_converter value.class
22
+ converter ? converter.from_db(value) : value
28
23
  end
29
24
 
30
25
  private
31
26
 
32
- def to_db_mapping_for(db, collection_name)
33
- key = [db.opts[:database], collection_name]
34
-
35
- @to_db_mapping[key] ||= begin
36
- columns = Hash[db.schema(collection_name)]
27
+ def from_db_converters
28
+ @from_db_converters ||= {}
29
+ end
37
30
 
38
- columns.each_with_object({}) do |(name, schema), hash|
39
- CONVERTERS.each do |converter|
40
- unless hash.key? name
41
- match = converter.column_type_regex.match schema[:db_type]
31
+ def to_db_converters
32
+ @to_db_converters ||= {}
33
+ end
34
+
35
+ def find_to_db_converter_and_type(db, collection_name, attribute_name)
36
+ key = [db.opts[:database], collection_name].join('.')
42
37
 
43
- hash[name] = {converter: converter, sub_type: match.captures.first} if match
44
- end
45
- end
38
+ to_db_converters[key] ||= begin
39
+ columns = Hash[db.schema(collection_name)]
40
+ to_db_converters[key] = columns.each_with_object({}) do |(name, schema), hash|
41
+ converter = CONVERTERS.detect { |c| c.to_db? schema[:db_type] }
42
+ hash[name] = [converter, schema[:db_type]] if converter
46
43
  end
47
44
  end
45
+
46
+ to_db_converters[key].fetch(attribute_name, [])
48
47
  end
49
48
 
50
- def from_db_mapping
51
- @from_db_mapping ||= begin
52
- CONVERTERS.each_with_object({}) do |converter, result|
53
- converter.db_classes.each do |db_class|
54
- result[db_class] = converter
55
- end
56
- end
49
+ def find_from_db_converter(klass)
50
+ if !from_db_converters.key?(klass)
51
+ from_db_converters[klass] = CONVERTERS.detect { |c| c.from_db? klass }
57
52
  end
53
+
54
+ from_db_converters[klass]
58
55
  end
59
56
 
60
57
  end
61
-
62
58
  end
63
59
  end
64
60
  end
@@ -3,28 +3,30 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class Array
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^([a-z]+)\[\]$/
8
+ DB_TYPE_REGEX = /^([a-z]+)\[\]$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
15
+ sub_type = type[0..-3]
14
16
  array = sub_type == 'hstore' ? value.map { |v| Sequel.hstore v } : value
15
17
  Sequel.pg_array array, sub_type
16
18
  end
17
19
 
18
- def db_classes
19
- [Sequel::Postgres::PGArray]
20
+ def from_db?(klass)
21
+ defined?(Sequel::Postgres::PGArray) &&
22
+ klass == Sequel::Postgres::PGArray
20
23
  end
21
24
 
22
- def from_db(object)
23
- object.to_a
25
+ def from_db(value)
26
+ value.to_a
24
27
  end
25
28
 
26
29
  end
27
-
28
30
  end
29
31
  end
30
32
  end
@@ -3,27 +3,28 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class HStore
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^hstore$/
8
+ DB_TYPE_REGEX = /^hstore$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
14
15
  Sequel.hstore value
15
16
  end
16
17
 
17
- def db_classes
18
- [Sequel::Postgres::HStore]
18
+ def from_db?(klass)
19
+ defined?(Sequel::Postgres::HStore) &&
20
+ klass == Sequel::Postgres::HStore
19
21
  end
20
22
 
21
- def from_db(object)
22
- object.to_h
23
+ def from_db(value)
24
+ value.to_h
23
25
  end
24
26
 
25
27
  end
26
-
27
28
  end
28
29
  end
29
30
  end
@@ -3,36 +3,39 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class JSON
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^json$/
8
+ DB_TYPE_REGEX = /^json$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
14
15
  Sequel.pg_json value
15
16
  end
16
17
 
17
- def db_classes
18
- @db_classes ||= from_db_convertions.keys
18
+ def from_db?(klass)
19
+ to_hash?(klass) || to_array?(klass)
19
20
  end
20
21
 
21
- def from_db(object)
22
- object.public_send from_db_convertions[object.class]
22
+ def from_db(value)
23
+ to_hash?(value.class) ? value.to_h : value.to_a
23
24
  end
24
25
 
25
26
  private
26
27
 
27
- def from_db_convertions
28
- @from_db_convertions ||= {
29
- Sequel::Postgres::JSONHash => :to_h,
30
- Sequel::Postgres::JSONArray => :to_a
31
- }
28
+ def to_hash?(klass)
29
+ defined?(Sequel::Postgres::JSONHash) &&
30
+ klass == Sequel::Postgres::JSONHash
32
31
  end
33
32
 
34
- end
33
+ def to_array?(klass)
34
+ defined?(Sequel::Postgres::JSONArray) &&
35
+ klass == Sequel::Postgres::JSONArray
36
+ end
35
37
 
38
+ end
36
39
  end
37
40
  end
38
41
  end
@@ -3,36 +3,39 @@ module Rasti
3
3
  module TypeConverters
4
4
  module PostgresTypes
5
5
  class JSONB
6
-
7
6
  class << self
8
7
 
9
- def column_type_regex
10
- /^jsonb$/
8
+ DB_TYPE_REGEX = /^jsonb$/
9
+
10
+ def to_db?(type)
11
+ !type.match(DB_TYPE_REGEX).nil?
11
12
  end
12
13
 
13
- def to_db(value, sub_type)
14
+ def to_db(value, type)
14
15
  Sequel.pg_jsonb value
15
16
  end
16
17
 
17
- def db_classes
18
- @db_classes ||= from_db_convertions.keys
18
+ def from_db?(klass)
19
+ to_hash?(klass) || to_array?(klass)
19
20
  end
20
21
 
21
- def from_db(object)
22
- object.public_send from_db_convertions[object.class]
22
+ def from_db(value)
23
+ to_hash?(value.class) ? value.to_h : value.to_a
23
24
  end
24
25
 
25
26
  private
26
27
 
27
- def from_db_convertions
28
- @from_db_convertions ||= {
29
- Sequel::Postgres::JSONBHash => :to_h,
30
- Sequel::Postgres::JSONBArray => :to_a
31
- }
28
+ def to_hash?(klass)
29
+ defined?(Sequel::Postgres::JSONBHash) &&
30
+ klass == Sequel::Postgres::JSONBHash
32
31
  end
33
32
 
34
- end
33
+ def to_array?(klass)
34
+ defined?(Sequel::Postgres::JSONBArray) &&
35
+ klass == Sequel::Postgres::JSONBArray
36
+ end
35
37
 
38
+ end
36
39
  end
37
40
  end
38
41
  end