Module: DatastaxRails::Schema::Cassandra

Included in:
Migrator
Defined in:
lib/datastax_rails/schema/cassandra.rb

Overview

Methods for managing the Cassandra schema

Instance Method Summary collapse

Instance Method Details

#cassandra_index_cql_name(cf, column) ⇒ Object

Computes the expected cassandra index name as reported by CQL.


103
104
105
# File 'lib/datastax_rails/schema/cassandra.rb', line 103

def cassandra_index_cql_name(cf, column)
  "#{cf}_#{column}_idx"
end

#check_missing_schema(model) ⇒ Object

Check for missing columns or columns needing cassandra indexes


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/datastax_rails/schema/cassandra.rb', line 6

def check_missing_schema(model) # rubocop:disable MethodLength
  count = 0
  model.attribute_definitions.each do |attribute, definition|
    unless column_exists?(model.column_family.to_s, attribute.to_s)
      count += 1
      say "Adding column '#{attribute}'", :subitem
      DatastaxRails::Cql::AlterColumnFamily.new(model.column_family).add(attribute => definition.cql_type).execute
    end
    if definition.options[:cql_index] && !definition.options[:solr_index]
      unless index_exists?(model.column_family.to_s, attribute.to_s)
        if index_exists?(model.column_family.to_s, attribute.to_s)
          count += 1
          say "Dropping solr index on #{attribute}", :subitem
          DatastaxRails::Cql::DropIndex.new(solr_index_cql_name(model.column_family.to_s, attribute.to_s)).execute
        end
        count += 1
        say "Creating cassandra index on #{attribute}", :subitem
        DatastaxRails::Cql::CreateIndex.new(
          cassandra_index_cql_name(
            model.column_family.to_s, attribute.to_s)
        ).on(model.column_family.to_s).column(attribute.to_s).execute
      end
    elsif definition.options[:cql_index]
      unless column_exists?(model.column_family.to_s, "__#{attribute}")
        # Create and populate the new column
        count += 1
        say "Adding column '__#{attribute}'", :subitem
        DatastaxRails::Cql::AlterColumnFamily.new(model.column_family)
          .add("__#{attribute}" => definition.cql_type).execute
        say "Populating column '__#{attribute}' (this might take a while)", :subitem
        export = "echo \"copy #{model.column_family} (key, #{attribute}) " \
                 "TO 'dsr_export.csv';\" | cqlsh #{model.current_server}"
        import = "echo \"copy #{model.column_family} (key, __#{attribute}) " \
                 "FROM 'dsr_export.csv';\" | cqlsh #{model.current_server}"
        if system(export)
          system(import)
        else
          @errors << "Looks like you don't have a working cqlsh command in your path.\n" \
                     "Run the following two commands from a server with cqlsh:\n\n#{export}\n#{import}"
        end
      end
      count += 1
      say "Creating cassandra index on __#{attribute}", :subitem
      DatastaxRails::Cql::CreateIndex.new(
        cassandra_index_cql_name(
          model.column_family.to_s, "__#{attribute}")
      ).on(model.column_family.to_s).column("__#{attribute}").execute
    end
  end
  count
end

#column_exists?(cf, col) ⇒ Boolean

Checks the Cassandra system tables to see if a column exists on a column family

Returns:

  • (Boolean)

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/datastax_rails/schema/cassandra.rb', line 124

def column_exists?(cf, col)
  klass = OpenStruct.new(column_family: 'system.schema_columns', default_consistency: 'QUORUM')
  cql = DatastaxRails::Cql::ColumnFamily.new(klass)
  results = cql.select('count(*)')
            .conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf, 'column_name' => col).execute
  exists = results.first['count'] > 0
  unless exists
    # We need to check if it's part of an alias (ugh)
    klass = OpenStruct.new(column_family: 'system.schema_columnfamilies', default_consistency: 'QUORUM')
    cql = DatastaxRails::Cql::ColumnFamily.new(klass)
    results = cql.select('column_aliases, key_aliases, value_alias')
              .conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf).execute
    row = results.first
    exists = row['key_aliases'].include?(col.to_s) ||
             row['column_aliases'].include?(col.to_s) ||
             (row['value_alias'] && row['value_alias'].include?(col.to_s))
  end
  exists
end

#column_family_exists?(cf) ⇒ Boolean

Checks the Cassandra system tables to see if a column family exists

Returns:

  • (Boolean)

116
117
118
119
120
121
# File 'lib/datastax_rails/schema/cassandra.rb', line 116

def column_family_exists?(cf)
  klass = OpenStruct.new(column_family: 'system.schema_columnfamilies', default_consistency: 'QUORUM')
  cql = DatastaxRails::Cql::ColumnFamily.new(klass)
  results = cql.select('count(*)').conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf).execute
  results.first['count'] > 0
end

#create_cql3_column_family(model) ⇒ Object

Creates a CQL3 backed column family


59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/datastax_rails/schema/cassandra.rb', line 59

def create_cql3_column_family(model)
  say 'Creating Column Family via CQL3', :subitem
  columns = {}
  model.attribute_definitions.each { |k, col| columns[k] = col.cql_type }
  pk = model.primary_key.to_s
  if model.respond_to?(:cluster_by) && model.cluster_by.present?
    pk += ", #{model.cluster_by}"
  end
  cql = DatastaxRails::Cql::CreateColumnFamily.new(model.column_family).primary_key(pk).columns(columns)
  cql.with(model.create_options) if model.create_options
  cql.execute
end

#create_keyspace(keyspace, options = {}) ⇒ Object

Creates the named keyspace


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/datastax_rails/schema/cassandra.rb', line 73

def create_keyspace(keyspace, options = {})
  opts = { name:           keyspace.to_s,
           strategy_class: 'org.apache.cassandra.locator.NetworkTopologyStrategy' }
         .with_indifferent_access.merge(options)

  if keyspace_exists?(keyspace.to_s)
    say "Keyspace #{keyspace} already exists"
    return false
  else
    cql = DatastaxRails::Cql::CreateKeyspace.new(opts.delete(:name))
    cql.strategy_class(opts.delete(:strategy_class))
    strategy_options = opts.delete('strategy_options')
    cql.strategy_options(strategy_options.symbolize_keys)
    say "Creating keyspace #{keyspace}"
    cql.execute
    return true
  end
end

#drop_keyspaceObject


92
93
94
95
# File 'lib/datastax_rails/schema/cassandra.rb', line 92

def drop_keyspace
  say "Dropping keyspace #{@keyspace}"
  DatastaxRails::Cql::DropKeyspace.new(@keyspace.to_s).execute
end

#index_exists?(cf, col) ⇒ Boolean

Checks the Cassandra system tables to see if an index exists on a column family

Returns:

  • (Boolean)

145
146
147
148
149
150
151
# File 'lib/datastax_rails/schema/cassandra.rb', line 145

def index_exists?(cf, col)
  klass = OpenStruct.new(column_family: 'system.schema_columns', default_consistency: 'QUORUM')
  cql = DatastaxRails::Cql::ColumnFamily.new(klass)
  results = cql.select('index_name')
            .conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf, 'column_name' => col).execute
  results.first['index_name'] != nil
end

#keyspace_exists?(keyspace) ⇒ Boolean

Checks the Cassandra system tables to see if a keyspace exists

Returns:

  • (Boolean)

108
109
110
111
112
113
# File 'lib/datastax_rails/schema/cassandra.rb', line 108

def keyspace_exists?(keyspace)
  klass = OpenStruct.new(column_family: 'system.schema_keyspaces', default_consistency: 'QUORUM')
  cql = DatastaxRails::Cql::ColumnFamily.new(klass)
  results = cql.select('count(*)').conditions('keyspace_name' => keyspace).execute
  results.first['count'].to_i > 0
end

#solr_index_cql_name(cf, column) ⇒ Object

Computes the expected solr index name as reported by CQL.


98
99
100
# File 'lib/datastax_rails/schema/cassandra.rb', line 98

def solr_index_cql_name(cf, column)
  "#{@keyspace}_#{cf}_#{column}_index"
end