datastax_rails 1.2.3 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -8
  4. data/config/schema.xml.erb +22 -19
  5. data/config/solrconfig.xml.erb +1 -1
  6. data/lib/cql-rb_extensions.rb +27 -0
  7. data/lib/datastax_rails.rb +13 -17
  8. data/lib/datastax_rails/associations/association.rb +1 -4
  9. data/lib/datastax_rails/associations/collection_proxy.rb +0 -13
  10. data/lib/datastax_rails/attribute_assignment.rb +28 -91
  11. data/lib/datastax_rails/attribute_methods.rb +109 -44
  12. data/lib/datastax_rails/attribute_methods/before_type_cast.rb +71 -0
  13. data/lib/datastax_rails/attribute_methods/dirty.rb +52 -11
  14. data/lib/datastax_rails/attribute_methods/primary_key.rb +87 -0
  15. data/lib/datastax_rails/attribute_methods/read.rb +120 -0
  16. data/lib/datastax_rails/attribute_methods/typecasting.rb +52 -21
  17. data/lib/datastax_rails/attribute_methods/write.rb +59 -0
  18. data/lib/datastax_rails/base.rb +227 -236
  19. data/lib/datastax_rails/cassandra_only_model.rb +25 -19
  20. data/lib/datastax_rails/column.rb +384 -0
  21. data/lib/datastax_rails/connection.rb +12 -13
  22. data/lib/datastax_rails/cql/alter_column_family.rb +0 -1
  23. data/lib/datastax_rails/cql/base.rb +15 -3
  24. data/lib/datastax_rails/cql/column_family.rb +2 -2
  25. data/lib/datastax_rails/cql/create_column_family.rb +7 -18
  26. data/lib/datastax_rails/cql/delete.rb +4 -9
  27. data/lib/datastax_rails/cql/insert.rb +2 -8
  28. data/lib/datastax_rails/cql/select.rb +4 -4
  29. data/lib/datastax_rails/cql/update.rb +8 -17
  30. data/lib/datastax_rails/dynamic_model.rb +98 -0
  31. data/lib/datastax_rails/payload_model.rb +19 -31
  32. data/lib/datastax_rails/persistence.rb +39 -54
  33. data/lib/datastax_rails/railtie.rb +1 -0
  34. data/lib/datastax_rails/reflection.rb +1 -1
  35. data/lib/datastax_rails/relation.rb +20 -20
  36. data/lib/datastax_rails/relation/batches.rb +18 -16
  37. data/lib/datastax_rails/relation/facet_methods.rb +1 -1
  38. data/lib/datastax_rails/relation/finder_methods.rb +6 -10
  39. data/lib/datastax_rails/relation/search_methods.rb +62 -48
  40. data/lib/datastax_rails/rsolr_client_wrapper.rb +1 -1
  41. data/lib/datastax_rails/schema/cassandra.rb +34 -62
  42. data/lib/datastax_rails/schema/migrator.rb +9 -24
  43. data/lib/datastax_rails/schema/solr.rb +13 -30
  44. data/lib/datastax_rails/schema_cache.rb +67 -0
  45. data/lib/datastax_rails/timestamps.rb +84 -11
  46. data/lib/datastax_rails/types/dirty_collection.rb +88 -0
  47. data/lib/datastax_rails/types/dynamic_list.rb +14 -0
  48. data/lib/datastax_rails/types/dynamic_map.rb +32 -0
  49. data/lib/datastax_rails/types/dynamic_set.rb +10 -0
  50. data/lib/datastax_rails/util/solr_repair.rb +4 -5
  51. data/lib/datastax_rails/validations.rb +6 -12
  52. data/lib/datastax_rails/validations/uniqueness.rb +0 -4
  53. data/lib/datastax_rails/version.rb +1 -1
  54. data/lib/datastax_rails/wide_storage_model.rb +13 -29
  55. data/lib/schema_migration.rb +4 -0
  56. data/spec/datastax_rails/associations_spec.rb +0 -1
  57. data/spec/datastax_rails/attribute_methods_spec.rb +9 -6
  58. data/spec/datastax_rails/base_spec.rb +26 -0
  59. data/spec/datastax_rails/column_spec.rb +238 -0
  60. data/spec/datastax_rails/cql/select_spec.rb +1 -1
  61. data/spec/datastax_rails/cql/update_spec.rb +2 -2
  62. data/spec/datastax_rails/persistence_spec.rb +29 -15
  63. data/spec/datastax_rails/relation/batches_spec.rb +5 -5
  64. data/spec/datastax_rails/relation/finder_methods_spec.rb +0 -20
  65. data/spec/datastax_rails/relation/search_methods_spec.rb +8 -0
  66. data/spec/datastax_rails/relation_spec.rb +7 -0
  67. data/spec/datastax_rails/schema/migrator_spec.rb +5 -10
  68. data/spec/datastax_rails/schema/solr_spec.rb +1 -1
  69. data/spec/datastax_rails/types/dynamic_list_spec.rb +20 -0
  70. data/spec/datastax_rails/types/dynamic_map_spec.rb +22 -0
  71. data/spec/datastax_rails/types/dynamic_set_spec.rb +16 -0
  72. data/spec/dummy/config/application.rb +2 -1
  73. data/spec/dummy/config/datastax.yml +6 -3
  74. data/spec/dummy/config/environments/development.rb +4 -5
  75. data/spec/dummy/config/environments/test.rb +0 -5
  76. data/spec/dummy/log/development.log +18 -0
  77. data/spec/dummy/log/test.log +36 -0
  78. data/spec/feature/dynamic_fields_spec.rb +9 -0
  79. data/spec/feature/overloaded_tables_spec.rb +24 -0
  80. data/spec/spec_helper.rb +1 -1
  81. data/spec/support/default_consistency_shared_examples.rb +2 -2
  82. data/spec/support/models.rb +28 -14
  83. metadata +212 -188
  84. data/lib/datastax_rails/identity.rb +0 -64
  85. data/lib/datastax_rails/identity/abstract_key_factory.rb +0 -29
  86. data/lib/datastax_rails/identity/custom_key_factory.rb +0 -37
  87. data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +0 -10
  88. data/lib/datastax_rails/identity/natural_key_factory.rb +0 -39
  89. data/lib/datastax_rails/identity/uuid_key_factory.rb +0 -27
  90. data/lib/datastax_rails/type.rb +0 -16
  91. data/lib/datastax_rails/types.rb +0 -9
  92. data/lib/datastax_rails/types/array_type.rb +0 -86
  93. data/lib/datastax_rails/types/base_type.rb +0 -42
  94. data/lib/datastax_rails/types/binary_type.rb +0 -19
  95. data/lib/datastax_rails/types/boolean_type.rb +0 -22
  96. data/lib/datastax_rails/types/date_type.rb +0 -23
  97. data/lib/datastax_rails/types/float_type.rb +0 -18
  98. data/lib/datastax_rails/types/integer_type.rb +0 -18
  99. data/lib/datastax_rails/types/string_type.rb +0 -16
  100. data/lib/datastax_rails/types/text_type.rb +0 -15
  101. data/lib/datastax_rails/types/time_type.rb +0 -23
  102. data/spec/datastax_rails/types/float_type_spec.rb +0 -31
  103. data/spec/datastax_rails/types/integer_type_spec.rb +0 -31
  104. data/spec/datastax_rails/types/time_type_spec.rb +0 -28
@@ -4,7 +4,6 @@ module DatastaxRails#:nodoc:
4
4
  def initialize(cf_name)
5
5
  @cf_name = cf_name
6
6
  @action = nil
7
- @consistency = 'QUORUM'
8
7
  end
9
8
 
10
9
  def add(column)
@@ -3,15 +3,21 @@ module DatastaxRails
3
3
  class Base
4
4
  # Base initialize that sets the default consistency.
5
5
  def initialize(klass, *args)
6
- @consistency = klass.default_consistency.to_s.upcase
6
+ @consistency = klass.default_consistency.to_s.downcase.to_sym
7
7
  @keyspace = DatastaxRails::Base.config[:keyspace]
8
+ @values = []
9
+ end
10
+
11
+ def using(consistency)
12
+ @consistency = consistency.to_s.downcase.to_sym
13
+ self
8
14
  end
9
15
 
10
16
  def key_name
11
17
  @klass.key_factory.key_columns
12
18
  end
13
19
 
14
- # Abstract. Should be overridden by subclasses
20
+ # Abstract. Should be overridden by subclasses
15
21
  def to_cql
16
22
  raise NotImplementedError
17
23
  end
@@ -22,7 +28,13 @@ module DatastaxRails
22
28
  def execute
23
29
  cql = self.to_cql
24
30
  puts cql if ENV['DEBUG_CQL'] == 'true'
25
- DatastaxRails::Base.connection.execute_cql_query(cql, :consistency => CassandraCQL::Thrift::ConsistencyLevel.const_get(@consistency || 'QUORUM'))
31
+ pp @values if ENV['DEBUG_CQL'] == 'true'
32
+ if(@values.present?)
33
+ stmt = DatastaxRails::Base.connection.prepare(cql)
34
+ stmt.execute(*@values, :consistency => @consistency)
35
+ else
36
+ DatastaxRails::Base.connection.execute(cql, :consistency => @consistency)
37
+ end
26
38
  end
27
39
  end
28
40
  end
@@ -30,8 +30,8 @@ module DatastaxRails
30
30
  DatastaxRails::Cql::Truncate.new(@klass)
31
31
  end
32
32
 
33
- def update(*keys)
34
- DatastaxRails::Cql::Update.new(@klass, keys.flatten)
33
+ def update(key)
34
+ DatastaxRails::Cql::Update.new(@klass, key)
35
35
  end
36
36
  end
37
37
  end
@@ -5,22 +5,11 @@ module DatastaxRails#:nodoc:
5
5
  @cf_name = cf_name
6
6
  @columns = {}
7
7
  @storage_parameters = []
8
- @key_type = 'uuid'
9
- @key_columns = @key_name = "key"
8
+ @primary_key = 'id'
10
9
  end
11
-
12
- def key_type(key_type)
13
- @key_type = key_type
14
- self
15
- end
16
-
17
- def key_name(key_name)
18
- @key_name = key_name
19
- self
20
- end
21
-
22
- def key_columns(key_columns)
23
- @key_columns = key_columns
10
+
11
+ def primary_key(pk)
12
+ @primary_key = pk
24
13
  self
25
14
  end
26
15
 
@@ -48,14 +37,14 @@ module DatastaxRails#:nodoc:
48
37
  end
49
38
 
50
39
  def to_cql
51
- stmt = "CREATE COLUMNFAMILY #{@cf_name} (#{@key_name} #{@key_type}, "
40
+ stmt = "CREATE COLUMNFAMILY #{@cf_name} ("
52
41
  @columns.each do |name,type|
53
42
  stmt << "#{name} #{type}, "
54
43
  end
55
- stmt << "PRIMARY KEY (#{@key_columns}))"
44
+ stmt << "PRIMARY KEY (#{@primary_key}))"
56
45
  unless @storage_parameters.empty?
57
46
  stmt << " WITH "
58
- stmt << @storage_parameters.join(" AND ")
47
+ stmt << @storage_parameters.flatten.join(" AND ")
59
48
  end
60
49
 
61
50
  stmt
@@ -7,15 +7,10 @@ module DatastaxRails
7
7
  @timestamp = nil
8
8
  @columns = []
9
9
  @conditions = {}
10
- @key_name = "key"
10
+ @key_name = @klass.primary_key
11
11
  super
12
12
  end
13
13
 
14
- def using(consistency)
15
- @consistency = consistency
16
- self
17
- end
18
-
19
14
  def columns(columns)
20
15
  @columns = columns
21
16
  self
@@ -37,7 +32,7 @@ module DatastaxRails
37
32
  end
38
33
 
39
34
  def to_cql
40
- values = [@keys.collect{|k|k.to_s}]
35
+ @values = @keys
41
36
  stmt = "DELETE #{@columns.join(',')} FROM #{@klass.column_family} "
42
37
 
43
38
  if(@timestamp)
@@ -48,10 +43,10 @@ module DatastaxRails
48
43
 
49
44
  @conditions.each do |col,val|
50
45
  stmt << " AND #{col} = ?"
51
- values << val
46
+ @values << val
52
47
  end
53
48
 
54
- CassandraCQL::Statement.sanitize(stmt, values)
49
+ stmt
55
50
  end
56
51
  end
57
52
  end
@@ -9,11 +9,6 @@ module DatastaxRails
9
9
  super
10
10
  end
11
11
 
12
- def using(consistency)
13
- @consistency = consistency
14
- self
15
- end
16
-
17
12
  def columns(columns)
18
13
  @columns.merge!(columns)
19
14
  self
@@ -30,11 +25,10 @@ module DatastaxRails
30
25
  end
31
26
 
32
27
  def to_cql
33
- values = []
34
28
  keys = []
35
29
  @columns.each do |k,v|
36
30
  keys << k.to_s
37
- values << v
31
+ @values << v
38
32
  end
39
33
  stmt = "INSERT INTO #{@klass.column_family} (#{keys.join(',')}) VALUES (#{('?'*keys.size).split(//).join(',')}) "
40
34
 
@@ -46,7 +40,7 @@ module DatastaxRails
46
40
  stmt << "AND TIMESTAMP #{@timestamp}"
47
41
  end
48
42
 
49
- CassandraCQL::Statement.sanitize(stmt, values).force_encoding('UTF-8')
43
+ stmt.force_encoding('UTF-8')
50
44
  end
51
45
  end
52
46
  end
@@ -44,15 +44,15 @@ module DatastaxRails#:nodoc:
44
44
 
45
45
  def to_cql
46
46
  conditions = []
47
- values = []
48
47
  stmt = "SELECT #{@select} FROM #{@klass.column_family} "
49
48
 
50
49
  if @paginate
51
- conditions << "token(key) > token('#{@paginate}')"
50
+ conditions << "token(#{@klass.primary_key}) > token(?)"
51
+ @values << @paginate
52
52
  end
53
53
 
54
54
  @conditions.each do |k,v|
55
- values << v
55
+ @values << v
56
56
  if v.kind_of?(Array)
57
57
  conditions << "\"#{k.to_s}\" IN (?)"
58
58
  else
@@ -76,7 +76,7 @@ module DatastaxRails#:nodoc:
76
76
  stmt << "ALLOW FILTERING "
77
77
  end
78
78
 
79
- CassandraCQL::Statement.sanitize(stmt, values)
79
+ stmt
80
80
  end
81
81
  end
82
82
  end
@@ -8,11 +8,6 @@ module DatastaxRails
8
8
  super
9
9
  end
10
10
 
11
- def using(consistency)
12
- @consistency = consistency
13
- self
14
- end
15
-
16
11
  def columns(columns)
17
12
  @columns.merge!(columns)
18
13
  self
@@ -36,7 +31,6 @@ module DatastaxRails
36
31
  def to_cql
37
32
  column_names = @columns.keys
38
33
 
39
-
40
34
  stmt = "update #{@klass.column_family} "
41
35
 
42
36
  if(@ttl)
@@ -49,22 +43,19 @@ module DatastaxRails
49
43
 
50
44
  unless @columns.empty?
51
45
  stmt << "SET "
52
-
53
- first_entry = column_names.first
54
-
55
- stmt << CassandraCQL::Statement.sanitize("\"#{first_entry.to_s}\" = ?", [@columns[first_entry]])
56
- column_names[1..-1].each do |col|
57
- stmt << CassandraCQL::Statement.sanitize(", \"#{col.to_s}\" = ?", [@columns[col]])
46
+ updates = []
47
+ @columns.each do |k,v|
48
+ @values << v
49
+ updates << "\"#{k}\" = ?"
58
50
  end
51
+
52
+ stmt << updates.join(", ")
59
53
  end
60
54
 
61
- stmt << CassandraCQL::Statement.sanitize(" WHERE key IN (?)", [@key])
55
+ stmt << " WHERE #{@klass.primary_key} IN (?)"
56
+ @values << @key
62
57
  stmt.force_encoding('UTF-8')
63
58
  end
64
-
65
- # def execute
66
- # puts to_cql.truncate(50)
67
- # end
68
59
  end
69
60
  end
70
61
  end
@@ -0,0 +1,98 @@
1
+ module DatastaxRails
2
+ # An extension to Wide Storage Models that let you index any arbitrary
3
+ # field that you want (given certain naming conventions).
4
+ #
5
+ # Try to keep the group_by as sort as possible since it will get stored
6
+ # with every attribute. Static attributes are only supported if they
7
+ # are included on every dynamic model that uses the same column family.
8
+ #
9
+ # Dynamic models have the following attributes:
10
+ # * strings
11
+ # * texts
12
+ # * booleans
13
+ # * dates
14
+ # * timestamps
15
+ # * integers
16
+ # * floats
17
+ # * uuids
18
+ #
19
+ # Each of these is a map that let's you store key/value pairs where the
20
+ # key is always a String and the value is a type that matches what would
21
+ # be stored in a static attribute of the same time. Everything will get
22
+ # typecasted, so you can safely store strings in it in all the same cases
23
+ # that you store strings in normal attributes.
24
+ #
25
+ # The advantage here is that you don't have to pre-define your schema
26
+ # ahead of time. The keys of any attributes added to this collection become
27
+ # fields in your Solr document.
28
+ #
29
+ # NOTE: due to the way fields dynamically map between Solr and Cassandra,
30
+ # the field name in Solr will have a prefix prepended to it. With the
31
+ # exception of timestamps, it is simply the first letter of the type
32
+ # followed by an underscore (_). So s_ for strings. Timestamp has a
33
+ # ts_ prefix to differentiate it from texts.
34
+ #
35
+ # class Item < DatastaxRails::DynamicModel
36
+ # self.group_by = 'item'
37
+ # timestamps
38
+ # end
39
+ #
40
+ # class CoreMetadata < DatastaxRails::DynamicModel
41
+ # self.group_by = 'core'
42
+ # timestamps
43
+ # end
44
+ #
45
+ # class TeamMetadata < DatastaxRails::DynamicModel
46
+ # self.group_by = 'team'
47
+ # timestamps
48
+ # end
49
+ #
50
+ # item = Item.create(strings: {title: "Title"})
51
+ # CoreMetadata.create(id: item.id, strings: {author: 'John'}, dates: {published_on: Date.today})
52
+ # TeamMetadata.create(id: item.id, booleans: {reviewed: true})
53
+ #
54
+ # CoreMetadata.where(s_author: 'John') #=> Finds the CoreMetadata record
55
+ # Item.fulltext("Title") #=> Finds the Item record
56
+ # Item.fulltext("John") #=> Doesn't find a record, but...
57
+ # Item.fulltext("{!join from=id to=id}John") #=> Does find the record by doing a Solr join across the entire row
58
+ #
59
+ # NOTE that the mapping of key names is happening automatically when you insert something into
60
+ # the collection so:
61
+ #
62
+ # Item.first.strings #=> {s_title: "Title"}
63
+ class DynamicModel < WideStorageModel
64
+ self.abstract_class = true
65
+
66
+ class_attribute :group_by_attribute
67
+
68
+ def self.group_by=(group)
69
+ self.group_by_attribute = group
70
+ self.attribute_definitions['group'].default = group
71
+ default_scope -> {where('group' => group)}
72
+ end
73
+
74
+
75
+ def self.inherited(child)
76
+ super
77
+ child.column_family = 'dynamic_model'
78
+ child.primary_key = 'id'
79
+ child.cluster_by = 'group'
80
+ child.uuid :id
81
+ child.string :group
82
+ child.map :s_, :holds => :string
83
+ child.map :t_, :holds => :text
84
+ child.map :b_, :holds => :boolean
85
+ child.map :d_, :holds => :date
86
+ child.map :ts_, :holds => :timestamp
87
+ child.map :i_, :holds => :integer
88
+ child.map :f_, :holds => :float
89
+ child.map :u_, :holds => :uuid
90
+
91
+ child.map_columns.each do |col|
92
+ child.instance_eval do
93
+ alias_attribute col.options[:holds].to_s.pluralize, col.name
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -5,7 +5,7 @@ module DatastaxRails
5
5
  # other metadata, you will need another model that points at this
6
6
  # one.
7
7
  #
8
- # class AttachmentPayload < DatastaxRails::Payload
8
+ # class AttachmentPayload < DatastaxRails::PayloadModel
9
9
  # self.column_family = 'attachment_payloads'
10
10
  #
11
11
  # validate do
@@ -14,14 +14,18 @@ module DatastaxRails
14
14
  # end
15
15
  # end
16
16
  # end
17
- class PayloadModel < CassandraOnlyModel
17
+ class PayloadModel < WideStorageModel
18
+ include CassandraOnlyModel
18
19
  self.abstract_class = true
19
20
 
20
21
  def self.inherited(child)
21
22
  super
22
- child.key :natural, :attributes => :digest
23
+ child.primary_key = 'digest'
24
+ child.cluster_by = 'chunk'
25
+ child.create_options = 'COMPACT STORAGE'
23
26
  child.string :digest
24
27
  child.binary :payload
28
+ child.integer :chunk
25
29
  child.validates :digest, :presence => true
26
30
  end
27
31
 
@@ -31,24 +35,26 @@ module DatastaxRails
31
35
  c.using(options[:consistency]) if options[:consistency]
32
36
  io = StringIO.new("","w+")
33
37
  found = false
34
- CassandraCQL::Result.new(c.execute).fetch do |row|
35
- io << Base64.decode64(row.to_hash['payload'])
38
+ chunk = 0
39
+ c.execute.each do |row|
40
+ io << Base64.decode64(row['payload'])
41
+ chunk = row['chunk']
36
42
  found = true
37
43
  end
38
44
  raise DatastaxRails::RecordNotFound unless found
39
45
  io.rewind
40
- self.instantiate(digest, {:digest => digest, :payload => io.read}, [:digest, :payload])
46
+ self.instantiate(digest, {:digest => digest, :payload => io.read, :chunk => chunk}, [:digest, :payload])
41
47
  end
42
48
 
43
- def self.write(key, attributes, options = {})
49
+ def self.write(record, options = {})
44
50
  raise ArgumentError, "'#{options[:consistency]}' is not a valid Cassandra consistency level" unless valid_consistency?(options[:consistency].to_s.upcase) if options[:consistency]
45
- c = self.cql.select("count(*)").conditions(:digest => key)
46
- count = CassandraCQL::Result.new(c.execute).fetch.to_hash["count"]
51
+ c = self.cql.select("count(*)").conditions(:digest => record.id)
52
+ count = c.execute.first["count"]
47
53
 
48
54
  i = 0
49
- io = StringIO.new(attributes['payload'])
55
+ io = StringIO.new(record.attributes['payload'])
50
56
  while chunk = io.read(1.megabyte)
51
- c = cql.insert.columns(:digest => key, :chunk => i, :payload => Base64.encode64(chunk))
57
+ c = cql.insert.columns(:digest => record.id, :chunk => i, :payload => Base64.encode64(chunk))
52
58
  c.using(options[:consistency]) if options[:consistency]
53
59
  c.execute
54
60
  i += 1
@@ -56,31 +62,13 @@ module DatastaxRails
56
62
 
57
63
  if count and count > i
58
64
  i.upto(count) do |j|
59
- c = cql.delete(key.to_s).key_name('digest').conditions(:chunk => j)
65
+ c = cql.delete(record.id).key_name('digest').conditions(:chunk => j)
60
66
  c.using(options[:consistency]) if options[:consistency]
61
67
  c.execute
62
68
  end
63
69
  end
64
70
 
65
- key
66
- end
67
-
68
- # Instantiates a new object without calling +initialize+.
69
- #
70
- # @param [String] key the primary key for the record
71
- # @param [Hash] attributes a hash containing the columns to set on the record
72
- # @param [Array] selected_attributes an array containing the attributes that were originally selected from cassandra
73
- # to build this object. Used so that we can avoid lazy-loading attributes that don't exist.
74
- # @return [DatastaxRails::Base] a model with the given attributes
75
- def self.instantiate(key, attributes, selected_attributes = [])
76
- allocate.tap do |object|
77
- object.instance_variable_set("@loaded_attributes", {}.with_indifferent_access)
78
- object.instance_variable_set("@key", parse_key(key)) if key
79
- object.instance_variable_set("@new_record", false)
80
- object.instance_variable_set("@destroyed", false)
81
- object.instance_variable_set("@attributes", attributes.with_indifferent_access)
82
- attributes.keys.each {|k| object.instance_variable_get("@loaded_attributes")[k] = true}
83
- end
71
+ record.id
84
72
  end
85
73
  end
86
74
  end