repertoire-faceting 0.5.5 → 0.6.0
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.
- checksums.yaml +4 -4
- data/FAQ +23 -17
- data/INSTALL +52 -84
- data/LICENSE +1 -1
- data/README +213 -34
- data/TODO +20 -7
- data/ext/Makefile +24 -14
- data/ext/README.faceting +51 -0
- data/ext/bytea/bytea.sql +173 -0
- data/ext/bytea/faceting_bytea.control +6 -0
- data/ext/common/util.sql +35 -0
- data/ext/faceting--0.6.0.sql +251 -0
- data/ext/faceting_bytea--0.6.0.sql +207 -0
- data/ext/faceting_varbit--0.6.0.sql +198 -0
- data/ext/signature/faceting.control +6 -0
- data/ext/signature/signature.c +740 -0
- data/ext/{signature.o → signature/signature.o} +0 -0
- data/ext/{signature.so → signature/signature.so} +0 -0
- data/ext/signature/signature.sql +217 -0
- data/ext/varbit/faceting_varbit.control +7 -0
- data/ext/varbit/varbit.sql +164 -0
- data/{public → lib/assets}/images/repertoire-faceting/proportional_symbol.png +0 -0
- data/{public → lib/assets}/images/repertoire-faceting/spinner_sm.gif +0 -0
- data/{public → lib/assets}/javascripts/rep.faceting/context.js +2 -2
- data/{public → lib/assets}/javascripts/rep.faceting/ext/earth_facet.js +2 -4
- data/{public → lib/assets}/javascripts/rep.faceting/facet.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting/facet_widget.js +3 -8
- data/{public → lib/assets}/javascripts/rep.faceting/nested_facet.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting/results.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting.js +5 -1
- data/{public → lib/assets}/javascripts/rep.protovis-facets.js +3 -3
- data/lib/assets/javascripts/rep.widgets/events.js +51 -0
- data/lib/assets/javascripts/rep.widgets/global.js +50 -0
- data/lib/assets/javascripts/rep.widgets/model.js +159 -0
- data/lib/assets/javascripts/rep.widgets/widget.js +213 -0
- data/lib/assets/javascripts/rep.widgets.js +14 -0
- data/{public → lib/assets}/stylesheets/rep.faceting.css +1 -1
- data/lib/repertoire-faceting/adapters/postgresql_adapter.rb +107 -48
- data/lib/repertoire-faceting/facets/abstract_facet.rb +43 -27
- data/lib/repertoire-faceting/facets/basic_facet.rb +23 -22
- data/lib/repertoire-faceting/facets/nested_facet.rb +50 -27
- data/lib/repertoire-faceting/model.rb +101 -65
- data/lib/repertoire-faceting/rails/engine.rb +8 -0
- data/lib/repertoire-faceting/rails/postgresql_adapter.rb +0 -1
- data/lib/repertoire-faceting/rails/relation.rb +0 -1
- data/lib/repertoire-faceting/railtie.rb +0 -1
- data/lib/repertoire-faceting/relation/calculations.rb +7 -2
- data/lib/repertoire-faceting/relation/query_methods.rb +17 -4
- data/lib/repertoire-faceting/routing.rb +2 -5
- data/lib/repertoire-faceting/tasks/all.rake +5 -4
- data/lib/repertoire-faceting/tasks/client.rake +2 -5
- data/lib/repertoire-faceting/version.rb +1 -1
- data/lib/repertoire-faceting.rb +2 -4
- data/{public → vendor/assets}/javascripts/google-earth-extensions.js +0 -0
- data/{public → vendor/assets}/javascripts/protovis.js +0 -0
- metadata +78 -78
- data/ext/README.signature +0 -33
- data/ext/signature.c +0 -740
- data/ext/signature.sql +0 -342
- data/ext/signature.sql.IN +0 -342
- data/ext/uninstall_signature.sql +0 -4
- data/ext/uninstall_signature.sql.IN +0 -4
- data/lib/repertoire-faceting/adapters/abstract_adapter.rb +0 -18
- data/lib/repertoire-faceting/relation/spawn_methods.rb +0 -26
@@ -3,54 +3,70 @@ require 'active_support/core_ext/object/blank'
|
|
3
3
|
module Repertoire
|
4
4
|
module Faceting
|
5
5
|
module Facets #:nodoc:
|
6
|
-
|
6
|
+
|
7
7
|
# Abstract interface all facet implementations must fulfil. At minimum, implementors should
|
8
8
|
# define self.claim?(), signature(), and drill() to create a new kind of facet.
|
9
9
|
#
|
10
|
-
# For indexing support, implement create_index
|
11
|
-
# in signature() and drill(), and act accordingly.
|
10
|
+
# For indexing support, implement create_index(), refresh_index(), and drop_index(). Detect the
|
11
|
+
# index's presence in signature() and drill(), and act accordingly.
|
12
|
+
#
|
13
|
+
# N.B. Facet instances can assume they have been mixed in with an ActiveRecord::Relation and
|
14
|
+
# Repertoire::Faceting::Model (see the mixin() method below). Think of them as scoped
|
15
|
+
# relations that identify an attribute of the model dataset.
|
12
16
|
#
|
13
17
|
# See BasicFacet and NestedFacet for examples of facet implementations.
|
18
|
+
#
|
14
19
|
module AbstractFacet
|
15
|
-
|
20
|
+
|
16
21
|
attr_accessor :facet_name
|
17
|
-
|
22
|
+
|
18
23
|
# Return true if this facet implementation can index this ActiveRecord relation. If multiple
|
19
|
-
# implementations claim a facet, the one that is laoded last wins.
|
24
|
+
# implementations claim a facet, the one that is laoded last wins.
|
20
25
|
def self.claim?(relation)
|
21
|
-
raise
|
26
|
+
raise "Please implement claim? for your facet"
|
22
27
|
end
|
23
28
|
|
24
29
|
# Return an arel expression for the signature of all models matching the given current refinement state for
|
25
|
-
# this facet (and ignoring all others).
|
30
|
+
# this facet (and ignoring all others). Signatures should be constructed from the column returned by
|
31
|
+
# faceting_id().
|
26
32
|
def signature(state)
|
27
|
-
raise
|
33
|
+
raise "Please implement signature for your facet"
|
28
34
|
end
|
29
|
-
|
30
|
-
# Return an arel expression for state, signature pairs from which one can refine the current state of this facet
|
31
|
-
# (ignoring all others)
|
35
|
+
|
36
|
+
# Return an arel expression for (state, signature) pairs from which one can refine the current state of this facet
|
37
|
+
# (ignoring all others). Signatures should be constructed from the column returned by faceting_id().
|
32
38
|
def drill(state)
|
33
|
-
raise
|
39
|
+
raise "Please implement drill for your facet"
|
34
40
|
end
|
35
41
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
def create_index(faceting_id)
|
41
|
-
raise 'Facet #{facet_name} does not support indexing'
|
42
|
+
# Create this facet's index table, or raise an exception if indexing not supported. Signatures should be
|
43
|
+
# constructed from the column returned by faceting_id().
|
44
|
+
def create_index
|
45
|
+
raise "Facet #{facet_name} does not support indexing"
|
42
46
|
end
|
43
47
|
|
44
|
-
|
48
|
+
# Refresh this facet's index, or raise an exception if it is not indexed
|
49
|
+
def refresh_index
|
50
|
+
raise "Facet #{facet_name} is not indexed" unless facet_indexed?
|
51
|
+
connection.refresh_materialized_view(facet_index_name)
|
52
|
+
end
|
45
53
|
|
46
|
-
#
|
47
|
-
def
|
48
|
-
|
54
|
+
# Drop this facet's index, or raise an exception if it is not indexed
|
55
|
+
def drop_index
|
56
|
+
raise "Facet #{facet_name} is not indexed" unless facet_indexed?
|
57
|
+
connection.drop_materialized_view(facet_index_name)
|
49
58
|
end
|
50
59
|
|
51
60
|
# Returns true if the facet's index table exists
|
52
|
-
def
|
53
|
-
|
61
|
+
def facet_indexed?
|
62
|
+
indexed_facets.map(&:to_sym).include?(facet_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# Return a facet's index table name
|
68
|
+
def facet_index_name
|
69
|
+
connection.facet_table_name(table_name, facet_name)
|
54
70
|
end
|
55
71
|
|
56
72
|
def self.implementations
|
@@ -70,10 +86,10 @@ module Repertoire
|
|
70
86
|
# mix in facet implementation and configure
|
71
87
|
relation.singleton_class.send(:include, impls.last)
|
72
88
|
relation.facet_name = name
|
73
|
-
|
89
|
+
|
74
90
|
relation
|
75
91
|
end
|
76
|
-
|
92
|
+
|
77
93
|
end
|
78
94
|
end
|
79
95
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Repertoire #:nodoc:
|
2
2
|
module Faceting #:nodoc:
|
3
3
|
module Facets #:nodoc:
|
4
|
-
|
5
|
-
# Implementation of AbstractFacet for non-nested, single-valued facets. By default,
|
4
|
+
|
5
|
+
# Implementation of AbstractFacet for non-nested, single-valued facets. By default,
|
6
6
|
# all facets that have a single group column will follow this behavior.
|
7
7
|
#
|
8
8
|
# See Repertoire::Faceting::Model::ClassMethods for usage.
|
@@ -10,56 +10,57 @@ module Repertoire #:nodoc:
|
|
10
10
|
module BasicFacet
|
11
11
|
include AbstractFacet
|
12
12
|
include Arel
|
13
|
-
|
13
|
+
|
14
14
|
def self.claim?(relation)
|
15
15
|
relation.group_values.size == 1
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def signature(state)
|
19
|
-
return read_index(state, true) if
|
19
|
+
return read_index(state, true) if facet_indexed?
|
20
20
|
col = group_values.first
|
21
21
|
rel = only(:where, :joins)
|
22
22
|
rel = rel.where(in_clause(col, state)) unless state.empty?
|
23
|
-
rel.select("signature(#{table_name}.#{faceting_id})").arel
|
23
|
+
rel.select("facet.signature(#{table_name}.#{faceting_id})").arel
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def drill(state)
|
27
|
-
return read_index(state, false) if
|
28
|
-
col = group_values.first
|
27
|
+
return read_index(state, false) if facet_indexed?
|
28
|
+
col = group_values.first
|
29
29
|
rel = only(:where, :joins, :group)
|
30
30
|
rel = rel.where(in_clause(col, state)) unless state.empty?
|
31
|
-
rel.select(["#{col} AS #{facet_name}", "signature(#{table_name}.#{faceting_id})"]).arel
|
31
|
+
rel.select(["#{col} AS #{facet_name}", "facet.signature(#{table_name}.#{faceting_id})"]).arel
|
32
32
|
end
|
33
|
-
|
34
|
-
def create_index
|
33
|
+
|
34
|
+
def create_index
|
35
35
|
col = group_values.first
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
rel = only(:where, :joins, :group)
|
37
|
+
sql = rel.select(["#{col} AS #{facet_name}", "facet.signature(#{table_name}.#{faceting_id})"]).to_sql
|
38
|
+
|
39
|
+
connection.create_materialized_view(facet_index_name, sql)
|
39
40
|
end
|
40
41
|
|
41
42
|
private
|
42
|
-
|
43
|
+
|
43
44
|
def in_clause(col, values)
|
44
45
|
# ActiveRecord unhelpfully scatters wrong table names in predicates...
|
45
46
|
values = values.map { |v| connection.quote(v) }
|
46
47
|
"#{col} IN (#{values.join(', ')})"
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
def read_index(state, aggregate)
|
50
|
-
index = Arel::Table.new(
|
51
|
+
index = Arel::Table.new(facet_index_name)
|
51
52
|
rel = SelectManager.new Table.engine
|
52
|
-
|
53
|
+
|
53
54
|
rel.from index
|
54
55
|
rel.where(index[facet_name].in(state)) unless state.empty?
|
55
|
-
|
56
|
+
|
56
57
|
if aggregate
|
57
|
-
rel.project('collect(signature) AS signature')
|
58
|
+
rel.project('facet.collect(signature) AS signature')
|
58
59
|
else
|
59
60
|
rel.project(index[facet_name], index[:signature])
|
60
61
|
end
|
61
62
|
end
|
62
|
-
|
63
|
+
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Repertoire
|
2
2
|
module Faceting
|
3
3
|
module Facets
|
4
|
-
|
4
|
+
|
5
5
|
# Implementation of AbstractFacet for facets whose values fall into a nested taxonomy.
|
6
6
|
# By default, all facets that group several columns will follow this behavior.
|
7
7
|
#
|
@@ -10,66 +10,90 @@ module Repertoire
|
|
10
10
|
module NestedFacet
|
11
11
|
include AbstractFacet
|
12
12
|
include Arel
|
13
|
-
|
13
|
+
|
14
14
|
def self.claim?(relation)
|
15
15
|
relation.group_values.size > 1
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def signature(state)
|
19
|
-
return read_index(state, true) if
|
19
|
+
return read_index(state, true) if facet_indexed?
|
20
20
|
rel = only(:where, :joins)
|
21
21
|
bind_nest(group_values, state) do |expr, val|
|
22
22
|
rel = rel.where("#{expr} = #{connection.quote(val)}")
|
23
23
|
end
|
24
|
-
rel.select("signature(#{table_name}.#{faceting_id})").arel
|
24
|
+
rel.select("facet.signature(#{table_name}.#{faceting_id})").arel
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def drill(state)
|
28
|
-
return read_index(state, false) if
|
28
|
+
return read_index(state, false) if facet_indexed?
|
29
29
|
rel = only(:where, :joins)
|
30
30
|
grp = bind_nest(group_values, state) do |expr, val|
|
31
31
|
rel = rel.where("#{expr} = #{connection.quote(val)}")
|
32
32
|
end
|
33
|
-
rel.group(grp).select(["#{grp.last} AS #{facet_name}", "signature(#{table_name}.#{faceting_id})"]).arel
|
33
|
+
rel.group(grp).select(["#{grp.last} AS #{facet_name}", "facet.signature(#{table_name}.#{faceting_id})"]).arel
|
34
34
|
end
|
35
|
-
|
36
|
-
def create_index
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
|
36
|
+
def create_index
|
37
|
+
levels = group_values.length
|
38
|
+
|
39
|
+
# Construct expressions at each grouping level, right pad with nil
|
40
|
+
expr_drills = (0..levels-1).map do |i|
|
41
|
+
group_values[0..i] + (i+1..levels-1).collect { "NULL" }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Construct indexes for each drill level
|
45
|
+
queries = []
|
46
|
+
expr_drills.each_with_index do |exprs, level|
|
47
|
+
rel = only(:where, :joins)
|
48
|
+
exprs.zip(columns).each do |expr, col|
|
49
|
+
rel = rel.select("#{expr} AS #{col}")
|
50
|
+
end
|
51
|
+
rel = rel.group(columns[0..level])
|
52
|
+
|
53
|
+
queries << rel.select(["#{level+1} AS level", "facet.signature(#{table_name}.#{faceting_id})"]).to_sql
|
40
54
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
55
|
+
|
56
|
+
# Root of tree
|
57
|
+
empty_cols = columns.map { |col| "NULL AS #{col}"}
|
58
|
+
queries << only(:where).select(empty_cols + ["0 AS level", "facet.signature(#{table_name}.#{faceting_id})"]).to_sql
|
59
|
+
|
60
|
+
# Give the fullest index first (i.e. leaves of the tree), so the database
|
61
|
+
# can resolve types before encountering any NULL values (i.e. values of
|
62
|
+
# indeterminate type)
|
63
|
+
queries = queries.reverse
|
64
|
+
|
65
|
+
# The full index table is union of indices at each drill level
|
66
|
+
sql = queries.join(" UNION ")
|
67
|
+
|
68
|
+
connection.create_materialized_view(facet_index_name, sql)
|
45
69
|
end
|
46
|
-
|
70
|
+
|
47
71
|
private
|
48
72
|
|
49
73
|
def columns
|
50
|
-
(1..group_values.size).map { |i| "#{facet_name}#{i}"}
|
74
|
+
(1..group_values.size).map { |i| "#{facet_name}_#{i}"}
|
51
75
|
end
|
52
|
-
|
76
|
+
|
53
77
|
# Iterates over all the columns that have state in turn, and returns
|
54
78
|
# a grouping of the columns one level further nested
|
55
79
|
def bind_nest(cols, state, &block)
|
56
80
|
level = state.size
|
57
81
|
grp = cols[0..level] # advance one nest step
|
58
|
-
|
82
|
+
|
59
83
|
if level > 0
|
60
|
-
cols[0..level-1].zip(state).each do |col, val|
|
84
|
+
cols[0..level-1].zip(state).each do |col, val|
|
61
85
|
yield(col, val)
|
62
86
|
end
|
63
87
|
end
|
64
88
|
grp << "NULL::TEXT" if level >= cols.size
|
65
|
-
|
89
|
+
|
66
90
|
grp
|
67
91
|
end
|
68
|
-
|
92
|
+
|
69
93
|
def read_index(state, aggregate)
|
70
|
-
index = Table.new(
|
94
|
+
index = Table.new(facet_index_name)
|
71
95
|
rel = SelectManager.new Table.engine
|
72
|
-
|
96
|
+
|
73
97
|
rel.from index
|
74
98
|
grp = bind_nest(columns, state) do |col, val|
|
75
99
|
rel.where(index[col].eq(val))
|
@@ -84,7 +108,6 @@ module Repertoire
|
|
84
108
|
rel.project("#{grp.last} AS #{facet_name}", index[:signature])
|
85
109
|
end
|
86
110
|
end
|
87
|
-
|
88
111
|
end
|
89
112
|
end
|
90
113
|
end
|
@@ -2,15 +2,18 @@ module Repertoire
|
|
2
2
|
module Faceting
|
3
3
|
module Model #:nodoc:
|
4
4
|
extend ActiveSupport::Concern
|
5
|
-
|
5
|
+
|
6
6
|
SIGNATURE_WASTAGE_THRESHOLD = 0.15
|
7
7
|
DEFAULT_SIGNATURE_COLUMN = 'id'
|
8
8
|
PACKED_SIGNATURE_COLUMN = '_packed_id'
|
9
|
-
|
9
|
+
|
10
10
|
included do |base|
|
11
|
-
base.singleton_class.delegate :refine, :minimum, :nils, :reorder, :to_sql, :to => :
|
11
|
+
base.singleton_class.delegate :refine, :minimum, :nils, :reorder, :to_sql, :to => :scoped_all
|
12
|
+
|
13
|
+
base.class_attribute(:facets)
|
14
|
+
base.facets = {}
|
12
15
|
end
|
13
|
-
|
16
|
+
|
14
17
|
#
|
15
18
|
# == Facet declarations
|
16
19
|
#
|
@@ -28,12 +31,12 @@ module Repertoire
|
|
28
31
|
# end
|
29
32
|
#
|
30
33
|
# Implicitly, any facet declaration is an SQL aggregate that divides the attribute values into discrete
|
31
|
-
# groups. When no relation is provided, /model/.group(/facet name/) is assumed by default. So the
|
34
|
+
# groups. When no relation is provided, /model/.group(/facet name/) is assumed by default. So the
|
32
35
|
# discipline facet declaration above is equivalent to
|
33
36
|
#
|
34
37
|
# facet :discipline, group(:discipline)
|
35
38
|
#
|
36
|
-
# and the grouping on degree could be left out. You can use this behavior to construct a facet
|
39
|
+
# and the grouping on degree could be left out. You can use this behavior to construct a facet
|
37
40
|
# from differently-named columns:
|
38
41
|
#
|
39
42
|
# facet :balloon_color, group(:color)
|
@@ -80,13 +83,13 @@ module Repertoire
|
|
80
83
|
# To incorporate refinements on other facets on this model, use refine:
|
81
84
|
#
|
82
85
|
# Nobelist.refine(:nobel_year => 2001, :degree => 'Ph.D.').count(:discipline)
|
83
|
-
#
|
86
|
+
#
|
84
87
|
# If you provide multiple values for a simple facet refinement, they are interpreted as a logical "or":
|
85
88
|
#
|
86
89
|
# Nobelist.refine(:nobel_year => [2000, 2001]) # => 'WHERE name IN (2000, 2001)'
|
87
90
|
#
|
88
91
|
# In the case of a nested facet, multiple values identify levels in the taxonomy:
|
89
|
-
#
|
92
|
+
#
|
90
93
|
# Nobelist.refine(:birth_place => [ 'Ukraine', 'Kiev' ]).count(:nobel_year)
|
91
94
|
#
|
92
95
|
# Refinements are integrated into result queries automatically:
|
@@ -96,9 +99,9 @@ module Repertoire
|
|
96
99
|
# == Index access
|
97
100
|
#
|
98
101
|
# As you will have noted already, facet counts and queries are quite similar to their ActiveRecord/SQL
|
99
|
-
# counterparts. Behind the scenes, the Repertoire faceting code re-writes your query.
|
102
|
+
# counterparts. Behind the scenes, the Repertoire faceting code re-writes your query.
|
100
103
|
#
|
101
|
-
# Facets defined on associations are joined and limited automatically, and facet indices in the database
|
104
|
+
# Facets defined on associations are joined and limited automatically, and facet indices in the database
|
102
105
|
# are used wherever possible rather than querying the model table.
|
103
106
|
#
|
104
107
|
# == Facet registration
|
@@ -112,114 +115,147 @@ module Repertoire
|
|
112
115
|
# See AbstractFacet for more details.
|
113
116
|
#
|
114
117
|
module ClassMethods
|
115
|
-
|
118
|
+
|
116
119
|
# Declare a facet by name
|
117
120
|
def facet(name, rel=nil)
|
118
121
|
name = name.to_sym
|
119
122
|
|
120
123
|
# default: group by column with facet name, order by count descending
|
121
|
-
rel ||=
|
124
|
+
rel ||= scoped_all
|
122
125
|
rel = rel.group(name) if rel.group_values.empty?
|
123
126
|
rel = rel.order(["count DESC", "#{name} ASC"]) if rel.order_values.empty?
|
124
127
|
|
125
128
|
# locate facet implementation that can handle relation
|
126
129
|
facets[name] = Facets::AbstractFacet.mixin(name, rel)
|
127
130
|
end
|
128
|
-
|
129
|
-
# Accessor for the facet definitions
|
130
|
-
def facets
|
131
|
-
read_inheritable_attribute(:facets) || write_inheritable_attribute(:facets, {})
|
132
|
-
end
|
133
|
-
|
131
|
+
|
134
132
|
# Is there a facet by this name?
|
135
133
|
def facet?(name)
|
136
134
|
facets.key?(name.to_sym)
|
137
135
|
end
|
138
|
-
|
136
|
+
|
139
137
|
# All defined facets by name
|
140
138
|
def facet_names
|
141
139
|
facets.keys
|
142
140
|
end
|
143
|
-
|
144
|
-
#
|
145
|
-
|
141
|
+
|
142
|
+
# Returns a list of the facets that currently have indices declared
|
143
|
+
def indexed_facets
|
144
|
+
connection.indexed_facets(table_name)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Drops any unused facet indices, updates its packed ids, then recreates
|
148
|
+
# indices for the facets with the provided names. If no names are provided,
|
146
149
|
# then the existing facet indices are refreshed.
|
147
150
|
#
|
148
|
-
# If a signature id column name is provided, it will be used to build the
|
149
|
-
# bitset indices. Otherwise the indexer will add or remove a new packed
|
151
|
+
# If a signature id column name is provided, it will be used to build the
|
152
|
+
# bitset indices. Otherwise the indexer will add or remove a new packed
|
150
153
|
# id column as appropriate.
|
151
154
|
#
|
152
155
|
# Examples:
|
153
156
|
#
|
154
157
|
# === Refresh existing facet indices
|
155
158
|
#
|
156
|
-
# Nobelist.
|
159
|
+
# Nobelist.index_facets
|
157
160
|
#
|
158
161
|
# === Adjust which facets are indexed
|
159
162
|
#
|
160
|
-
# Nobelist.
|
163
|
+
# Nobelist.index_facets([:degree, :nobel_year])
|
161
164
|
#
|
162
165
|
# === Drop all facet indices, but add/remove packed id as necessary
|
163
166
|
#
|
164
|
-
# Nobelist.
|
167
|
+
# Nobelist.index_facets([])
|
165
168
|
#
|
166
|
-
# === Drop absolutely everything, force manual faceting using 'id'
|
169
|
+
# === Drop absolutely everything, force manual faceting using 'id'
|
167
170
|
# column
|
168
171
|
#
|
169
|
-
# Nobelist.
|
172
|
+
# Nobelist.index_facets([], 'id')
|
170
173
|
#
|
171
|
-
def
|
174
|
+
def index_facets(next_indexes=nil, next_faceting_id=nil)
|
172
175
|
# default: update existing facets
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
+
current_indexes = indexed_facets
|
177
|
+
next_indexes ||= current_indexes
|
178
|
+
|
179
|
+
# sanity checks
|
180
|
+
current_indexes = current_indexes.map { |name| name.to_sym }
|
181
|
+
next_indexes = next_indexes.map { |name| name.to_sym }
|
182
|
+
(current_indexes | next_indexes).each do
|
183
|
+
|name| raise QueryError, "Unknown facet #{name}" unless facet?(name)
|
184
|
+
end
|
185
|
+
|
176
186
|
# determine best column for signature bitsets, unless set manually
|
177
|
-
|
187
|
+
next_faceting_id ||= if signature_wastage(DEFAULT_SIGNATURE_COLUMN) < SIGNATURE_WASTAGE_THRESHOLD
|
178
188
|
DEFAULT_SIGNATURE_COLUMN
|
179
189
|
else
|
180
190
|
PACKED_SIGNATURE_COLUMN
|
181
191
|
end
|
182
|
-
|
192
|
+
|
193
|
+
# default behavior: no changes to packed id column
|
194
|
+
drop_packed_id = create_packed_id = false
|
195
|
+
|
196
|
+
# default behavior: adjust facet indexes
|
197
|
+
drop_list = current_indexes - next_indexes
|
198
|
+
refresh_list = next_indexes & current_indexes
|
199
|
+
create_list = next_indexes - current_indexes
|
200
|
+
|
201
|
+
# adding or removing a packed id column
|
202
|
+
if next_faceting_id != faceting_id
|
203
|
+
drop_packed_id = (next_faceting_id == DEFAULT_SIGNATURE_COLUMN)
|
204
|
+
create_packed_id = (next_faceting_id != DEFAULT_SIGNATURE_COLUMN)
|
205
|
+
end
|
206
|
+
|
207
|
+
# special case: repacking an existing packed id column
|
208
|
+
if next_faceting_id == faceting_id && next_faceting_id != DEFAULT_SIGNATURE_COLUMN
|
209
|
+
drop_packed_id = create_packed_id = (signature_wastage > SIGNATURE_WASTAGE_THRESHOLD)
|
210
|
+
end
|
211
|
+
|
212
|
+
# changing item ids invalidates all existing facet indices
|
213
|
+
if drop_packed_id || create_packed_id
|
214
|
+
drop_list, refresh_list, create_list = [ current_indexes, [], next_indexes ]
|
215
|
+
end
|
216
|
+
|
183
217
|
connection.transaction do
|
184
|
-
#
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
# re-create the facet indices
|
194
|
-
facet_names.each do |name|
|
195
|
-
name = name.to_sym
|
196
|
-
raise "Unknown facet #{name}" unless facet?(name)
|
197
|
-
facets[name].create_index(signature_column)
|
198
|
-
end
|
218
|
+
# adjust faceting id column
|
219
|
+
connection.remove_column(table_name, PACKED_SIGNATURE_COLUMN) if drop_packed_id
|
220
|
+
connection.add_column(table_name, PACKED_SIGNATURE_COLUMN, "SERIAL") if create_packed_id
|
221
|
+
@faceting_id = next_faceting_id
|
222
|
+
|
223
|
+
# adjust facet indices
|
224
|
+
drop_list.each { |name| facets[name].drop_index }
|
225
|
+
refresh_list.each { |name| facets[name].refresh_index }
|
226
|
+
create_list.each { |name| facets[name].create_index }
|
199
227
|
end
|
200
|
-
|
228
|
+
|
229
|
+
# TODO. in a nested transaction, this would need to fire after the final commit...
|
201
230
|
reset_column_information
|
231
|
+
|
202
232
|
end
|
203
|
-
|
233
|
+
|
234
|
+
# Over-rides reset_column_information in ActiveRecord::ModelSchema
|
235
|
+
def reset_column_information
|
236
|
+
@faceting_id = nil
|
237
|
+
super
|
238
|
+
end
|
239
|
+
|
204
240
|
# Returns the name of the id column to use for constructing bitset signatures
|
205
241
|
# over this model.
|
206
242
|
def faceting_id
|
207
|
-
[PACKED_SIGNATURE_COLUMN, DEFAULT_SIGNATURE_COLUMN].detect { |c| column_names.include?(c) }
|
243
|
+
@faceting_id ||= [PACKED_SIGNATURE_COLUMN, DEFAULT_SIGNATURE_COLUMN].detect { |c| column_names.include?(c) }
|
208
244
|
end
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
245
|
+
|
246
|
+
# Returns the proportion of wasted slots in 0..max(id)
|
247
|
+
def signature_wastage(signature_column = nil)
|
248
|
+
signature_column ||= faceting_id
|
249
|
+
connection.signature_wastage(table_name, signature_column)
|
213
250
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
end
|
251
|
+
|
252
|
+
# Once clients have migrated to Rails 4, delete and replace with 'all' where this is called
|
253
|
+
#
|
254
|
+
# c.f. http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it
|
255
|
+
def scoped_all
|
256
|
+
where(nil)
|
221
257
|
end
|
222
|
-
|
258
|
+
|
223
259
|
end
|
224
260
|
end
|
225
261
|
end
|
@@ -9,9 +9,14 @@ module Repertoire
|
|
9
9
|
def count(name = nil, options = {})
|
10
10
|
if name.present? && @klass.facet?(name)
|
11
11
|
name = name.to_sym
|
12
|
-
|
12
|
+
parent = @klass.facets[name]
|
13
|
+
facet = parent.merge(self)
|
13
14
|
state = refine_value[name] || []
|
14
15
|
signatures = facet.drill(state)
|
16
|
+
|
17
|
+
facet.minimum_value = self.minimum_value || parent.minimum_value
|
18
|
+
facet.nils_value = self.nils_value || parent.nils_value
|
19
|
+
|
15
20
|
connection.population(facet, masks, signatures)
|
16
21
|
else
|
17
22
|
super
|
@@ -39,7 +44,7 @@ module Repertoire
|
|
39
44
|
base = except(:order, :limit, :offset)
|
40
45
|
masks = []
|
41
46
|
|
42
|
-
masks << base.only(:where, :join).select("signature(#{table_name}.#{faceting_id})").arel if base.where_values.present?
|
47
|
+
masks << base.only(:where, :join).select("facet.signature(#{table_name}.#{faceting_id})").arel if base.where_values.present?
|
43
48
|
refine_value.each do |name, values|
|
44
49
|
raise QueryError, "Unkown facet #{name} on #{klass.name}" unless @klass.facet?(name)
|
45
50
|
masks << @klass.facets[name].signature(values)
|