rasti-db 2.0.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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