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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 210581882359ef05a1b09c69b172f259b0b5010b61a254442e9844158a4f9456
4
- data.tar.gz: 1dd3f0fcc8399e0145abc50166495a87826bb1b32e52de4acb45ea114f7650d5
3
+ metadata.gz: cbeaaf85ac611e3a8a014720fca0ce2328c240e73b05cae04c4f9c9a3be6b7f6
4
+ data.tar.gz: 120f2ad079c6ea185bf95d4e74eec3a4d85bbb5bfb6941e8b8f923d5653c5e5f
5
5
  SHA512:
6
- metadata.gz: 2c209c735eb50327d4bb7b54754530cd155dec884e32661f046de73dfb53fb036877e1e25f1ade565ef51a0517254096d24934f3d6e47393febc99318ea71118
7
- data.tar.gz: a5a8326418216e1558ffea4b93e52a2369420fed2c8d9633dda5ffd8ee34b457e06aaaea2018001ba8725e51c1ef44386a78e2abaa51819628eba93cec38da99
6
+ metadata.gz: a9479412975696063c5bf0d62e2f5a41d2f71afe98adb688356cd468a9dc6da04c9b02a5fcc434c8e3964d06bc1f96bfb6206fa6a8032e23587b1d68be9372b6
7
+ data.tar.gz: 5608471902a30b61971f03ad95e7b6d6d9fd62b8bf350d4bea55f840518e18f32201a9598f533882ee5a0c19d0634824d714fb3361f2f7fbdb976e4125819f46
data/README.md CHANGED
@@ -93,7 +93,7 @@ User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
93
93
  Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
94
94
  Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
95
95
  Category = Rasti::DB::Model[:id, :name, :posts]
96
- Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
96
+ Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :full_name, :birth_date, :user_id, :user]
97
97
  Language = Rasti::DB::Model[:id, :name, :people]
98
98
  ```
99
99
 
@@ -111,10 +111,10 @@ class Posts < Rasti::DB::Collection
111
111
  many_to_many :categories
112
112
  one_to_many :comments
113
113
 
114
- query :created_by do |user_id|
114
+ query :created_by do |user_id|
115
115
  where user_id: user_id
116
116
  end
117
-
117
+
118
118
  query :entitled, -> (title) { where title: title }
119
119
 
120
120
  query :commented_by do |user_id|
@@ -144,6 +144,10 @@ class People < Rasti::DB::Collection
144
144
 
145
145
  many_to_one :user
146
146
  many_to_many :languages
147
+
148
+ computed_attribute :full_name do
149
+ Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
150
+ end
147
151
  end
148
152
 
149
153
  class Languages < Rasti::DB::Collection
@@ -210,6 +214,10 @@ posts.all_attributes # => [Post, ...]
210
214
  posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
211
215
  posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
212
216
  posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
217
+
218
+ posts.each { |post| do_something post } # Iterate posts loading all at first
219
+ posts.each(batch_size: 1000) { |post| do_something post } # Iterate posts loading in batches
220
+ posts.each_batch(size: 1000) { |posts| do_something posts } # Iterate batches of posts
213
221
  ```
214
222
  ### Natural Query Language
215
223
 
data/lib/rasti/db.rb CHANGED
@@ -6,20 +6,25 @@ require 'treetop'
6
6
  require 'hierarchical_graph'
7
7
  require 'class_config'
8
8
  require 'hash_ext'
9
+ require 'inflecto'
9
10
  require 'multi_require'
10
11
 
11
12
  module Rasti
12
13
  module DB
13
-
14
+
14
15
  extend MultiRequire
15
16
  extend ClassConfig
16
17
 
17
18
  require_relative 'db/query'
18
19
  require_relative_pattern 'db/relations/*'
19
20
  require_relative_pattern 'db/type_converters/postgres_types/*'
21
+ require_relative_pattern 'db/type_converters/sqlite_types/*'
22
+ require_relative 'db/nql/nodes/constants/base'
23
+ require_relative_pattern 'db/nql/filter_condition_strategies/types/*'
20
24
  require_relative_pattern 'db/**/*'
21
25
 
22
26
  attr_config :type_converters, []
27
+ attr_config :nql_filter_condition_strategy, nil
23
28
 
24
29
  def self.to_db(db, collection_name, attribute_name, value)
25
30
  type_converters.inject(value) do |result, type_converter|
@@ -33,5 +38,10 @@ module Rasti
33
38
  end
34
39
  end
35
40
 
41
+ def self.nql_filter_condition_for(comparison_name, identifier, argument)
42
+ raise 'Undefined Rasti::DB.nql_filter_condition_strategy' unless nql_filter_condition_strategy
43
+ nql_filter_condition_strategy.filter_condition_for comparison_name, identifier, argument
44
+ end
45
+
36
46
  end
37
47
  end
@@ -14,7 +14,7 @@ module Rasti
14
14
  end
15
15
 
16
16
  def collection_attributes
17
- @collection_attributes ||= model.attributes - relations.keys
17
+ @collection_attributes ||= model.attributes - relations.keys - computed_attributes.keys
18
18
  end
19
19
 
20
20
  def primary_key
@@ -47,6 +47,10 @@ module Rasti
47
47
  @queries ||= Hash::Indifferent.new
48
48
  end
49
49
 
50
+ def computed_attributes
51
+ @computed_attributes ||= Hash::Indifferent.new
52
+ end
53
+
50
54
  private
51
55
 
52
56
  def set_collection_name(collection_name)
@@ -91,6 +95,11 @@ module Rasti
91
95
  end
92
96
  end
93
97
 
98
+ def computed_attribute(name, &block)
99
+ raise "Computed Attribute #{name} already exists" if computed_attributes.key? name
100
+ computed_attributes[name] = block.call
101
+ end
102
+
94
103
  end
95
104
 
96
105
  def initialize(environment)
@@ -0,0 +1,22 @@
1
+ module Rasti
2
+ module DB
3
+ class ComputedAttribute
4
+
5
+ attr_reader :identifier
6
+
7
+ def initialize(identifier, &join)
8
+ @identifier = identifier
9
+ @join = join
10
+ end
11
+
12
+ def apply_join(dataset)
13
+ join ? join.call(dataset) : dataset
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :join
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ class Base
6
+
7
+ def filter_condition_for(comparison_name, identifier, argument)
8
+ type = type_for argument
9
+ raise UnsupportedTypeComparison.new(type, comparison_name) unless type.respond_to?(comparison_name)
10
+ type.public_send comparison_name, identifier, argument.value
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ class Postgres < Base
6
+
7
+ PG_TYPES = {
8
+ array: Types::PGArray
9
+ }
10
+
11
+ private
12
+
13
+ def type_for(argument)
14
+ PG_TYPES.fetch(argument.type, Types::Generic)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ class SQLite < Base
6
+
7
+ SQLITE_TYPES = {
8
+ array: Types::SQLiteArray
9
+ }
10
+
11
+ private
12
+
13
+ def type_for(argument)
14
+ SQLITE_TYPES.fetch(argument.type, Types::Generic)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ module Types
6
+ class Generic
7
+
8
+ def self.equal(identifier, value)
9
+ {identifier => value}
10
+ end
11
+
12
+ def self.not_equal(identifier, value)
13
+ Sequel.negate equal(identifier, value)
14
+ end
15
+
16
+ def self.greater_than(identifier, value)
17
+ identifier > value
18
+ end
19
+
20
+ def self.greater_than_or_equal(identifier, value)
21
+ identifier >= value
22
+ end
23
+
24
+ def self.less_than(identifier, value)
25
+ identifier < value
26
+ end
27
+
28
+ def self.less_than_or_equal(identifier, value)
29
+ identifier <= value
30
+ end
31
+
32
+ def self.like(identifier, value)
33
+ Sequel.ilike identifier, value
34
+ end
35
+
36
+ def self.include(identifier, value)
37
+ Sequel.ilike identifier, "%#{value}%"
38
+ end
39
+
40
+ def self.not_include(identifier, value)
41
+ ~include(identifier, value)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ module Types
6
+ class PGArray
7
+
8
+ def self.equal(identifier, values)
9
+ Sequel.&(
10
+ Sequel.pg_array(identifier).contains(Sequel.pg_array(values)),
11
+ Sequel.pg_array(identifier).contained_by(Sequel.pg_array(values))
12
+ )
13
+ end
14
+
15
+ def self.not_equal(identifier, values)
16
+ ~equal(identifier, values)
17
+ end
18
+
19
+ def self.include(identifier, values)
20
+ Sequel.pg_array(identifier).overlaps Sequel.pg_array(values)
21
+ end
22
+
23
+ def self.not_include(identifier, values)
24
+ ~include(identifier, values)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ module Types
6
+ class SQLiteArray
7
+
8
+ def self.equal(identifier, values)
9
+ array = values.map { |value| "\"#{value}\"" }.join(',')
10
+ {identifier => "[#{array}]"}
11
+ end
12
+
13
+ def self.not_equal(identifier, values)
14
+ Sequel.|(*values.map { |value| ~Sequel.like(identifier, "%\"#{value}\"%") })
15
+ end
16
+
17
+ def self.like(identifier, values)
18
+ Sequel.|(*values.map { |value| Sequel.like(identifier, "%#{value}%") })
19
+ end
20
+
21
+ def self.include(identifier, values)
22
+ Sequel.|(*values.map { |value| Sequel.like(identifier, "%\"#{value}\"%") })
23
+ end
24
+
25
+ def self.not_include(identifier, values)
26
+ Sequel.&(*values.map { |value| ~Sequel.like(identifier, "%\"#{value}\"%") })
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module FilterConditionStrategies
5
+ class UnsupportedTypeComparison < StandardError
6
+
7
+ attr_reader :argument_type, :comparison_name
8
+
9
+ def initialize(argument_type, comparison_name)
10
+ @argument_type = argument_type
11
+ @comparison_name = comparison_name
12
+ end
13
+
14
+ def message
15
+ "Unsupported comparison #{comparison_name} for #{argument_type}"
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module Nodes
5
+ class ArrayContent < Treetop::Runtime::SyntaxNode
6
+
7
+ def values
8
+ [left.value] + right_value
9
+ end
10
+
11
+ private
12
+
13
+ def right_value
14
+ right.is_a?(self.class) ? right.values : [right.value]
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Rasti
2
+ module DB
3
+ module NQL
4
+ module Nodes
5
+ class Attribute < Treetop::Runtime::SyntaxNode
6
+
7
+ def identifier(collection_class)
8
+ if computed? collection_class
9
+ collection_class.computed_attributes[column].identifier
10
+ else
11
+ tables.empty? ? Sequel[column] : Sequel[tables.join('__').to_sym][column]
12
+ end
13
+ end
14
+
15
+ def tables
16
+ _tables.elements.map{ |e| e.table.text_value }
17
+ end
18
+
19
+ def column
20
+ _column.text_value.to_sym
21
+ end
22
+
23
+ def computed_attributes(collection_class)
24
+ computed?(collection_class) ? [column] : []
25
+ end
26
+
27
+ private
28
+
29
+ def computed?(collection_class)
30
+ collection_class.computed_attributes.key? column
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -8,6 +8,10 @@ module Rasti
8
8
  values.flat_map(&:dependency_tables)
9
9
  end
10
10
 
11
+ def computed_attributes(collection_class)
12
+ left.computed_attributes(collection_class) | right.computed_attributes(collection_class)
13
+ end
14
+
11
15
  def values
12
16
  @values ||= values_for(left) + values_for(right)
13
17
  end
@@ -6,7 +6,21 @@ module Rasti
6
6
  class Base < Treetop::Runtime::SyntaxNode
7
7
 
8
8
  def dependency_tables
9
- field.tables.empty? ? [] : [field.tables.join('.')]
9
+ attribute.tables.empty? ? [] : [attribute.tables.join('.')]
10
+ end
11
+
12
+ def computed_attributes(collection_class)
13
+ attribute.computed_attributes(collection_class)
14
+ end
15
+
16
+ def filter_condition(collection_class)
17
+ DB.nql_filter_condition_for comparison_name, attribute.identifier(collection_class), argument
18
+ end
19
+
20
+ private
21
+
22
+ def comparison_name
23
+ Inflecto.underscore(Inflecto.demodulize(self.class)).to_sym
10
24
  end
11
25
 
12
26
  end