Skip to content

Commit 66507c1

Browse files
Merge pull request #181 from datastax/RUBY-241
RUBY-241 - Don't expose materialized view's with nil base-table's.
2 parents 139fdf5 + c94a87e commit 66507c1

File tree

6 files changed

+126
-23
lines changed

6 files changed

+126
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Features:
44

55
Bug Fixes:
6+
# 3.0.3
7+
8+
Bug Fixes:
9+
* [RUBY-241](https://datastax-oss.atlassian.net/browse/RUBY-241) Materialied views sometimes have nil ref to base-table.
610

711
# 3.0.2
812

lib/cassandra/cluster/schema/fetchers.rb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,8 @@ def fetch_materialized_view(connection, keyspace_name, view_name)
107107
nil
108108
else
109109
view_row = rows_views.first
110-
base_table = @schema.keyspace(keyspace_name).table(view_row['base_table_name'])
111110
create_materialized_view(view_row,
112-
rows_columns,
113-
base_table)
111+
rows_columns)
114112
end
115113
end
116114
end
@@ -1216,10 +1214,8 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types,
12161214

12171215
views = rows_views.each_with_object({}) do |row, h|
12181216
view_name = row['view_name']
1219-
base_table = tables[row['base_table_name']]
12201217
h[view_name] = create_materialized_view(row,
12211218
lookup_columns[view_name],
1222-
base_table,
12231219
types)
12241220
end
12251221

@@ -1366,9 +1362,10 @@ def create_index(table, row_index)
13661362
options['target'], options))
13671363
end
13681364

1369-
def create_materialized_view(view_data, rows_columns, base_table, types = nil)
1365+
def create_materialized_view(view_data, rows_columns, types = nil)
13701366
keyspace_name = view_data['keyspace_name']
13711367
view_name = view_data['view_name']
1368+
base_table_name = view_data['base_table_name']
13721369
include_all_columns = view_data['include_all_columns']
13731370
where_clause = view_data['where_clause']
13741371

@@ -1406,7 +1403,7 @@ def create_materialized_view(view_data, rows_columns, base_table, types = nil)
14061403
view_options,
14071404
include_all_columns,
14081405
where_clause,
1409-
base_table,
1406+
base_table_name,
14101407
view_data['id'])
14111408
end
14121409
end

lib/cassandra/keyspace.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ def each_table(&block)
113113
# @return [Boolean] whether this keyspace has a materialized view with the given name
114114
# @param name [String] materialized view name
115115
def has_materialized_view?(name)
116-
@views.key?(name)
116+
# We check if the view exists *and* that its base-table is set. If base-table isn't available,
117+
# it will be soon, so the user can poll on this method until we return a fully-baked materialized view.
118+
@views.key?(name) && @views[name].base_table
117119
end
118120

119121
# @return [Cassandra::MaterializedView, nil] a materialized view or nil
120122
# @param name [String] materialized view name
121123
def materialized_view(name)
122-
@views[name]
124+
@views[name] if has_materialized_view?(name)
123125
end
124126

125127
# Yield or enumerate each materialized view defined in this keyspace
@@ -130,10 +132,16 @@ def materialized_view(name)
130132
# @return [Array<Cassandra::MaterializedView>] a list of materialized views
131133
def each_materialized_view(&block)
132134
if block_given?
133-
@views.each_value(&block)
135+
@views.each_value do |v|
136+
block.call(v) if v.base_table
137+
end
134138
self
135139
else
136-
@views.values
140+
result = []
141+
@views.each_value do |v|
142+
result << v if v.base_table
143+
end
144+
result
137145
end
138146
end
139147
alias materialized_views each_materialized_view

lib/cassandra/materialized_view.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ module Cassandra
2121
# @see Cassandra::Keyspace#each_materialized_view
2222
# @see Cassandra::Keyspace#materialized_view
2323
class MaterializedView < ColumnContainer
24-
# @return [Table] the table that this materialized view applies to.
25-
attr_reader :base_table
26-
2724
# @private
2825
def initialize(keyspace,
2926
name,
@@ -33,12 +30,17 @@ def initialize(keyspace,
3330
options,
3431
include_all_columns,
3532
where_clause,
36-
base_table,
33+
base_table_name,
3734
id)
3835
super(keyspace, name, partition_key, clustering_columns, other_columns, options, id)
3936
@include_all_columns = include_all_columns
4037
@where_clause = where_clause
41-
@base_table = base_table
38+
@base_table_name = base_table_name
39+
end
40+
41+
# @return [Table] the table that this materialized view applies to.
42+
def base_table
43+
@keyspace.table(@base_table_name)
4244
end
4345

4446
# @return [String] a cql representation of this materialized view
@@ -52,7 +54,7 @@ def to_cql
5254
Util.escape_name(column.name)
5355
end.join(', ')
5456
end
55-
cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}"
57+
cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table_name)}"
5658
cql << "\nWHERE #{@where_clause}" if @where_clause
5759
cql << "\nPRIMARY KEY (("
5860
cql << @partition_key.map do |column|
@@ -74,7 +76,7 @@ def eql?(other)
7476
super.eql?(other) &&
7577
@include_all_columns == other.include_all_columns &&
7678
@where_clause == other.where_clause &&
77-
@base_table == other.base_table
79+
@base_table_name == other.base_table.name
7880
end
7981
alias == eql?
8082

spec/cassandra/keyspace_spec.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# encoding: utf-8
2+
3+
#--
4+
# Copyright 2013-2016 DataStax, Inc.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#++
18+
19+
require 'spec_helper'
20+
21+
include Cassandra::Types
22+
module Cassandra
23+
describe(Keyspace) do
24+
let(:view) { double('view') }
25+
let(:table) { double('table') }
26+
let(:ks) { Keyspace.new('myks', true, nil, {'mytable' => table}, nil, nil, nil, {'myview' => view}) }
27+
28+
before do
29+
allow(view).to receive(:set_keyspace)
30+
allow(table).to receive(:set_keyspace)
31+
end
32+
33+
context :has_materialized_view? do
34+
it 'should return true if the view exists and has a base-table' do
35+
expect(view).to receive(:base_table).and_return(:table)
36+
expect(ks.has_materialized_view?('myview')).to be_truthy
37+
end
38+
39+
it 'should return false if the view exists but does not have a base-table' do
40+
expect(view).to receive(:base_table).and_return(nil)
41+
expect(ks.has_materialized_view?('myview')).to be_falsey
42+
end
43+
end
44+
45+
context :materialized_view do
46+
it 'should return the view if it exists and has a base-table' do
47+
expect(view).to receive(:base_table).and_return(:table)
48+
expect(ks.materialized_view('myview')).to be(view)
49+
end
50+
51+
it 'should return nil if the view exists but does not have a base-table' do
52+
expect(view).to receive(:base_table).and_return(nil)
53+
expect(ks.materialized_view('myview')).to be_nil
54+
end
55+
end
56+
57+
context :materialized_views do
58+
it 'should return the view if it exists and has a base-table' do
59+
expect(view).to receive(:base_table).and_return(:table)
60+
expect(ks.materialized_views).to eq([view])
61+
end
62+
63+
it 'should not return view if the view exists but does not have a base-table' do
64+
expect(view).to receive(:base_table).and_return(nil)
65+
expect(ks.materialized_views).to be_empty
66+
end
67+
end
68+
69+
context :each_materialized_view do
70+
it 'should return the view if it exists and has a base-table' do
71+
expect(view).to receive(:base_table).and_return(:table)
72+
result = []
73+
ks.each_materialized_view do |v|
74+
result << v
75+
end
76+
77+
expect(result).to eq([view])
78+
end
79+
80+
it 'should not return view if the view exists but does not have a base-table' do
81+
expect(view).to receive(:base_table).and_return(nil)
82+
result = []
83+
ks.each_materialized_view do |v|
84+
result << v
85+
end
86+
87+
expect(result).to be_empty
88+
end
89+
end
90+
end
91+
end

spec/cassandra/materialized_view_spec.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ module Cassandra
3434

3535
before do
3636
allow(ks).to receive(:name).and_return('myks1')
37+
allow(ks).to receive(:table).with('table1').and_return(table)
3738
allow(table).to receive(:name).and_return('table1')
3839
allow(col).to receive(:name).and_return('col')
3940
allow(col).to receive(:type).and_return(Cassandra::Types.int)
@@ -45,7 +46,7 @@ module Cassandra
4546
end
4647

4748
it 'should quote keyspace, view name, table name, columns properly' do
48-
t = MaterializedView.new(ks, 'myview1', [col], [], [col2, col3], options, false, where, table,id)
49+
t = MaterializedView.new(ks, 'myview1', [col], [], [col2, col3], options, false, where, 'table1', id)
4950
expected_cql = <<-EOF
5051
CREATE MATERIALIZED VIEW "myks1"."myview1" AS
5152
SELECT col, "col2", "from"
@@ -58,7 +59,7 @@ module Cassandra
5859
end
5960

6061
it 'should quote primary key properly for simple partition key' do
61-
t = MaterializedView.new(ks, 'myview1', [col], [col2], [col3], options, false, where, table,id)
62+
t = MaterializedView.new(ks, 'myview1', [col], [col2], [col3], options, false, where, 'table1', id)
6263
expected_cql = <<-EOF
6364
CREATE MATERIALIZED VIEW "myks1"."myview1" AS
6465
SELECT col, "col2", "from"
@@ -71,7 +72,7 @@ module Cassandra
7172
end
7273

7374
it 'should quote primary key properly for composite partition key' do
74-
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, where, table,id)
75+
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, where, 'table1', id)
7576
expected_cql = <<-EOF
7677
CREATE MATERIALIZED VIEW "myks1"."myview1" AS
7778
SELECT col, "col2", "from"
@@ -84,7 +85,7 @@ module Cassandra
8485
end
8586

8687
it 'should handle no where-clause properly' do
87-
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, nil, table,id)
88+
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, nil, 'table1', id)
8889
expected_cql = <<-EOF
8990
CREATE MATERIALIZED VIEW "myks1"."myview1" AS
9091
SELECT col, "col2", "from"
@@ -96,7 +97,7 @@ module Cassandra
9697
end
9798

9899
it 'should handle include-all-columns properly' do
99-
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, true, nil, table,id)
100+
t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, true, nil, 'table1', id)
100101
expected_cql = <<-EOF
101102
CREATE MATERIALIZED VIEW "myks1"."myview1" AS
102103
SELECT *

0 commit comments

Comments
 (0)