squint 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.all-contributorsrc +86 -0
- data/lib/squint.rb +96 -74
- data/lib/squint/version.rb +1 -1
- data/readme.md +185 -0
- data/test/dummy/config/application.rb +3 -1
- data/test/dummy/config/environments/test.rb +7 -2
- data/test/dummy/log/development.log +39 -0
- data/test/dummy/log/test.log +62346 -0
- data/test/reports/TEST-SquintTest.xml +57 -0
- data/test/squint_test.rb +18 -14
- data/test/test_helper.rb +5 -1
- metadata +23 -7
- data/test/dummy/test/models/post_test.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df371ee0e87ea3faac9e9fc9a41b348f0ff30424
|
4
|
+
data.tar.gz: d44f014f0e8fa11d00f23bf19a6fa29d4954b035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8459d887335bb272cbd9d0fb8e31f012d2c573e710c3dc2b2a4d6e07057187171c7dbc8e9fbd873cec58741199aae5804861698c9eba2bdfaf5fd6ae6e3f69b
|
7
|
+
data.tar.gz: d000d6ebde399bb8edaead6c27e43aeb78575b81072eb9d0f0aa96e860cfb61bf45d0e169a1830bf6f7428e4c0511cd4798ac1ae900f882a636276d3a35a2345
|
data/.all-contributorsrc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
{
|
2
|
+
"projectName": "squint",
|
3
|
+
"projectOwner": "ProctorU",
|
4
|
+
"files": [
|
5
|
+
"readme.md"
|
6
|
+
],
|
7
|
+
"imageSize": 100,
|
8
|
+
"commit": true,
|
9
|
+
"contributors": [
|
10
|
+
{
|
11
|
+
"login": "chevinbrown",
|
12
|
+
"name": "Kevin Brown",
|
13
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/864581?v=3",
|
14
|
+
"profile": "https://github.com/chevinbrown",
|
15
|
+
"contributions": [
|
16
|
+
"design",
|
17
|
+
"review"
|
18
|
+
]
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"login": "king601",
|
22
|
+
"name": "Andrew Fomera",
|
23
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/1741179?v=3",
|
24
|
+
"profile": "http://andrewfomera.com",
|
25
|
+
"contributions": [
|
26
|
+
"review"
|
27
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"login": "Jaehdawg",
|
31
|
+
"name": "Matthew Jaeh",
|
32
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/1785682?v=3",
|
33
|
+
"profile": "https://github.com/Jaehdawg",
|
34
|
+
"contributions": [
|
35
|
+
"design",
|
36
|
+
"review"
|
37
|
+
]
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"login": "rthbound",
|
41
|
+
"name": "Ryan T. Hosford",
|
42
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/708692?v=3",
|
43
|
+
"profile": "https://github.com/rthbound",
|
44
|
+
"contributions": [
|
45
|
+
"code"
|
46
|
+
]
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"login": "licatajustin",
|
50
|
+
"name": "Justin Licata",
|
51
|
+
"avatar_url": "https://avatars0.githubusercontent.com/u/3933204?v=3",
|
52
|
+
"profile": "https://twitter.com/justinlicata",
|
53
|
+
"contributions": [
|
54
|
+
"code",
|
55
|
+
"design",
|
56
|
+
"doc",
|
57
|
+
"review"
|
58
|
+
]
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"login": "dwilkins",
|
62
|
+
"name": "David H. Wilkins",
|
63
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/97011?v=3",
|
64
|
+
"profile": "http://conecuh.com",
|
65
|
+
"contributions": [
|
66
|
+
"question",
|
67
|
+
"bug",
|
68
|
+
"code",
|
69
|
+
"design",
|
70
|
+
"doc",
|
71
|
+
"example",
|
72
|
+
"review",
|
73
|
+
"test"
|
74
|
+
]
|
75
|
+
},
|
76
|
+
{
|
77
|
+
"login": "TheJayWright",
|
78
|
+
"name": "Jay Wright",
|
79
|
+
"avatar_url": "https://avatars3.githubusercontent.com/u/19173815?v=3",
|
80
|
+
"profile": "https://github.com/TheJayWright",
|
81
|
+
"contributions": [
|
82
|
+
"review"
|
83
|
+
]
|
84
|
+
}
|
85
|
+
]
|
86
|
+
}
|
data/lib/squint.rb
CHANGED
@@ -3,23 +3,34 @@ require 'active_support/concern'
|
|
3
3
|
# Squint json, jsonb, hstore queries
|
4
4
|
module Squint
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
|
6
|
+
if ActiveRecord::VERSION::STRING < '5'
|
7
|
+
include ::ActiveRecord::QueryMethods
|
8
|
+
end
|
7
9
|
|
8
10
|
module WhereMethods
|
9
|
-
# Args may be passed to build_where like:
|
11
|
+
# Args may be passed to build/build_where like:
|
10
12
|
# build_where(jsonb_column: {key1: value1})
|
11
13
|
# build_where(jsonb_column: {key1: value1}, jsonb_column: {key2: value2})
|
12
14
|
# build_where(jsonb_column: {key1: value1}, regular_column: value)
|
13
15
|
# build_where(jsonb_column: {key1: value1}, association: {column: value))
|
14
|
-
|
16
|
+
if ActiveRecord::VERSION::STRING > '5'
|
17
|
+
method_name = :build
|
18
|
+
elsif ActiveRecord::VERSION::STRING < '5'
|
19
|
+
method_name = :build_where
|
20
|
+
end
|
21
|
+
send :define_method, method_name do |*args|
|
22
|
+
# For Rails 5, we end up monkey patching WhereClauseFactory for everyone
|
23
|
+
# so need to return super if our methods aren't on the AR class
|
24
|
+
# doesn't hurt for 4.2.x either
|
25
|
+
return super(*args) unless klass.respond_to?(:squint_hash_field_reln)
|
15
26
|
save_args = []
|
16
27
|
reln = args.inject([]) do |memo, arg|
|
17
28
|
if arg.is_a?(Hash)
|
18
29
|
arg.keys.each do |key|
|
19
30
|
if arg[key].is_a?(Hash) && HASH_DATA_COLUMNS[key]
|
20
|
-
memo <<
|
31
|
+
memo << klass.squint_hash_field_reln(key => arg[key])
|
21
32
|
else
|
22
|
-
|
33
|
+
save_args << { key => arg[key] }
|
23
34
|
end
|
24
35
|
end
|
25
36
|
elsif arg.present?
|
@@ -27,15 +38,51 @@ module Squint
|
|
27
38
|
end
|
28
39
|
memo
|
29
40
|
end
|
41
|
+
if ActiveRecord::VERSION::STRING > '5'
|
42
|
+
reln = ActiveRecord::Relation::WhereClause.new(reln, [])
|
43
|
+
save_args << [] if save_args.size == 1
|
44
|
+
end
|
30
45
|
reln += super(*save_args) unless save_args.empty?
|
31
46
|
reln
|
32
47
|
end
|
48
|
+
end
|
49
|
+
|
50
|
+
included do |base|
|
51
|
+
if ActiveRecord::VERSION::STRING < '5'
|
52
|
+
ar_reln_module = base::ActiveRecord_Relation
|
53
|
+
ar_association_module = base::ActiveRecord_AssociationRelation
|
54
|
+
elsif ActiveRecord::VERSION::STRING > '5.1'
|
55
|
+
# ActiveRecord_Relation is now a private_constant in 5.1.x
|
56
|
+
ar_reln_module = base.relation_delegate_class(ActiveRecord::Relation)::WhereClauseFactory
|
57
|
+
ar_association_module = nil
|
58
|
+
elsif ActiveRecord::VERSION::STRING > '5.0'
|
59
|
+
ar_reln_module = base::ActiveRecord_Relation::WhereClauseFactory
|
60
|
+
ar_association_module = nil
|
61
|
+
# ar_association_module = base::ActiveRecord_AssociationRelation
|
62
|
+
end
|
63
|
+
|
64
|
+
# put together a list of columns in this model
|
65
|
+
# that are hstore, json, or jsonb and will benefit from
|
66
|
+
# searchability
|
67
|
+
HASH_DATA_COLUMNS = base.columns_hash.keys.map do |col_name|
|
68
|
+
if %w[hstore json jsonb].include?(base.columns_hash[col_name].sql_type)
|
69
|
+
[col_name.to_sym, base.columns_hash[col_name].sql_type]
|
70
|
+
end
|
71
|
+
end.compact.to_h
|
33
72
|
|
34
|
-
|
73
|
+
ar_reln_module.class_eval do
|
74
|
+
prepend WhereMethods
|
75
|
+
end
|
76
|
+
|
77
|
+
ar_association_module.try(:class_eval) do
|
78
|
+
prepend WhereMethods
|
79
|
+
end
|
80
|
+
|
81
|
+
# squint_hash_field_reln
|
35
82
|
# return an Arel object with the appropriate query
|
36
83
|
# Strings want to be a SQL Literal, other things can be
|
37
84
|
# passed in bare to the eq or in operator
|
38
|
-
def
|
85
|
+
def self.squint_hash_field_reln(*args)
|
39
86
|
temp_attr = args[0]
|
40
87
|
contains_nil = false
|
41
88
|
column_type = HASH_DATA_COLUMNS[args[0].keys.first]
|
@@ -100,109 +147,84 @@ module Squint
|
|
100
147
|
# specified as a query value
|
101
148
|
if check_attr_missing
|
102
149
|
reln = if column_type == 'hstore'.freeze
|
103
|
-
|
150
|
+
squint_hstore_element_missing(column_name_segments, reln)
|
104
151
|
else
|
105
|
-
|
152
|
+
squint_jsonb_element_missing(column_name_segments, reln)
|
106
153
|
end
|
107
154
|
end
|
108
155
|
reln
|
109
156
|
end
|
110
|
-
end
|
111
|
-
|
112
|
-
included do |base|
|
113
|
-
ar_reln_module = base::ActiveRecord_Relation
|
114
|
-
ar_association_module = base::ActiveRecord_AssociationRelation
|
115
157
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
158
|
+
def self.squint_storext_default?(temp_attr, attribute_sym)
|
159
|
+
return false unless respond_to?(:storext_definitions)
|
160
|
+
if storext_definitions.keys.include?(attribute_sym) &&
|
161
|
+
!(storext_definitions[attribute_sym][:opts] &&
|
162
|
+
storext_definitions[attribute_sym][:opts][:default]).nil? &&
|
163
|
+
[temp_attr].compact.map(&:to_s).
|
164
|
+
flatten.
|
165
|
+
include?(storext_definitions[attribute_sym][:opts][:default].to_s)
|
166
|
+
true
|
122
167
|
end
|
123
|
-
end.compact.to_h
|
124
|
-
|
125
|
-
ar_reln_module.class_eval do
|
126
|
-
prepend WhereMethods
|
127
168
|
end
|
128
169
|
|
129
|
-
|
130
|
-
|
170
|
+
def self.squint_hstore_element_exists(element, attribute_hash_column, value)
|
171
|
+
Arel::Nodes::Equality.new(
|
172
|
+
Arel::Nodes::NamedFunction.new(
|
173
|
+
"exist",
|
174
|
+
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
175
|
+
Arel::Nodes::SqlLiteral.new(element)]
|
176
|
+
), value
|
177
|
+
)
|
131
178
|
end
|
132
179
|
|
133
|
-
def self.
|
180
|
+
def self.squint_hstore_element_missing(column_name_segments, reln)
|
134
181
|
element = column_name_segments.pop
|
135
182
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
136
183
|
# Query generated is equals default or attribute present is null or equals false
|
137
|
-
# * Is null happens
|
184
|
+
# * Is null happens the the column is null
|
138
185
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
139
186
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
140
|
-
# (("posts"."storext_attributes"
|
141
|
-
# ("posts"."storext_attributes"
|
187
|
+
# (exists("posts"."storext_attributes", 'is_awesome') IS NULL OR
|
188
|
+
# exists("posts"."storext_attributes", 'is_awesome') = FALSE)
|
142
189
|
# )
|
143
190
|
Arel::Nodes::Grouping.new(
|
144
191
|
reln.or(
|
145
192
|
Arel::Nodes::Grouping.new(
|
146
|
-
Arel::Nodes::
|
147
|
-
|
148
|
-
|
149
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
150
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
151
|
-
Arel::Nodes::SqlLiteral.new(element)
|
152
|
-
)
|
153
|
-
), nil
|
154
|
-
).or(
|
155
|
-
Arel::Nodes::Equality.new(
|
156
|
-
Arel::Nodes::Grouping.new(
|
157
|
-
Arel::Nodes::InfixOperation.new(
|
158
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
159
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
160
|
-
Arel::Nodes::SqlLiteral.new(element)
|
161
|
-
)
|
162
|
-
), Arel::Nodes::False.new
|
163
|
-
)
|
164
|
-
)
|
193
|
+
squint_hstore_element_exists(element, attribute_hash_column, Arel::Nodes::False.new)
|
194
|
+
).or(
|
195
|
+
squint_hstore_element_exists(element, attribute_hash_column, nil)
|
165
196
|
)
|
166
197
|
)
|
167
198
|
)
|
168
199
|
end
|
169
200
|
|
170
|
-
def self.
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
201
|
+
def self.squint_jsonb_element_equality(element, attribute_hash_column, value)
|
202
|
+
Arel::Nodes::Equality.new(
|
203
|
+
Arel::Nodes::Grouping.new(
|
204
|
+
Arel::Nodes::InfixOperation.new(
|
205
|
+
Arel::Nodes::SqlLiteral.new('?'),
|
206
|
+
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
207
|
+
Arel::Nodes::SqlLiteral.new(element)
|
208
|
+
)
|
209
|
+
), value
|
210
|
+
)
|
179
211
|
end
|
180
212
|
|
181
|
-
def self.
|
213
|
+
def self.squint_jsonb_element_missing(column_name_segments, reln)
|
182
214
|
element = column_name_segments.pop
|
183
215
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
184
216
|
# Query generated is equals default or attribute present is null or equals false
|
185
|
-
# * Is null happens the the column is null
|
217
|
+
# * Is null happens when the the whole column is null
|
186
218
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
187
219
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
188
|
-
# (
|
189
|
-
#
|
220
|
+
# (("posts"."storext_attributes" ? 'is_awesome') IS NULL OR
|
221
|
+
# ("posts"."storext_attributes" ? 'is_awesome') = FALSE)
|
190
222
|
# )
|
191
223
|
Arel::Nodes::Grouping.new(
|
192
224
|
reln.or(
|
193
225
|
Arel::Nodes::Grouping.new(
|
194
|
-
|
195
|
-
|
196
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
197
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
198
|
-
).eq(Arel::Nodes::False.new)
|
199
|
-
).or(
|
200
|
-
Arel::Nodes::Equality.new(
|
201
|
-
Arel::Nodes::NamedFunction.new(
|
202
|
-
"exist",
|
203
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
204
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
205
|
-
), nil
|
226
|
+
squint_jsonb_element_equality(element, attribute_hash_column, nil).or(
|
227
|
+
squint_jsonb_element_equality(element, attribute_hash_column, Arel::Nodes::False.new)
|
206
228
|
)
|
207
229
|
)
|
208
230
|
)
|
data/lib/squint/version.rb
CHANGED
data/readme.md
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://twitter.com/ProctorUEng">
|
3
|
+
<img src="https://s3-us-west-2.amazonaws.com/dev-team-resources/squint-wordmark.svg" width=198 height=72>
|
4
|
+
</a>
|
5
|
+
|
6
|
+
<p align="center">
|
7
|
+
Search PostgreSQL <code>jsonb</code> and <code>hstore</code> columns.
|
8
|
+
</p>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<br>
|
12
|
+
|
13
|
+
> Full database searching inside columns containing semi-structured data like `json`,
|
14
|
+
`jsonb` and `hstore`. <strong>Compatible with the awesome
|
15
|
+
<a href="https://github.com/G5/storext">storext</a> gem</strong>.
|
16
|
+
|
17
|
+
## Table of contents
|
18
|
+
|
19
|
+
- [Status](#status)
|
20
|
+
- [Quick start](#quick-start)
|
21
|
+
- [Performance](#performance)
|
22
|
+
- [Storext attributes](#storext-attributes)
|
23
|
+
- [Developing](#developing)
|
24
|
+
- [Contributors](#contributors)
|
25
|
+
- [Credits](#credits)
|
26
|
+
|
27
|
+
## Status
|
28
|
+
[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)
|
29
|
+
[![CircleCI](https://circleci.com/gh/ProctorU/squint.svg?style=svg)](https://circleci.com/gh/ProctorU/squint)
|
30
|
+
|
31
|
+
## Quick Start
|
32
|
+
|
33
|
+
Add to your Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'squint'
|
37
|
+
```
|
38
|
+
|
39
|
+
Include it in your models:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Post < ActiveRecord::Base
|
43
|
+
include Squint
|
44
|
+
# ...
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Assuming a table with the following structure:
|
49
|
+
```
|
50
|
+
Table "public.posts"
|
51
|
+
Column | Type | Modifiers
|
52
|
+
---------------------------+-----------------------------+----------------------------------------------------
|
53
|
+
id | integer | not null default nextval('posts_id_seq'::regclass)
|
54
|
+
title | character varying |
|
55
|
+
body | character varying |
|
56
|
+
request_info | jsonb |
|
57
|
+
properties | hstore |
|
58
|
+
storext_jsonb_attributes | jsonb |
|
59
|
+
storext_hstore_attributes | jsonb |
|
60
|
+
created_at | timestamp without time zone | not null
|
61
|
+
updated_at | timestamp without time zone | not null
|
62
|
+
Indexes:
|
63
|
+
"posts_pkey" PRIMARY KEY, btree (id)
|
64
|
+
```
|
65
|
+
|
66
|
+
In your code use queries like:
|
67
|
+
```ruby
|
68
|
+
Post.where(properties: { referer: 'http://example.com/one' } )
|
69
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."properties"->'referer' = 'http://example.com/one'
|
70
|
+
|
71
|
+
Post.where(properties: { referer: nil } )
|
72
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."properties"->'referer' IS NULL
|
73
|
+
|
74
|
+
Post.where(properties: { referer: ['http://example.com/one',nil] } )
|
75
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."properties"->'referer' = 'http://example.com/one'
|
76
|
+
# OR "posts"."properties"->'referer' IS NULL)
|
77
|
+
|
78
|
+
Post.where(request_info: { referer: ['http://example.com/one',nil] } )
|
79
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."request_info"->>'referer' = 'http://example.com/one'
|
80
|
+
# OR "posts"."request_info"->>'referer' IS NULL)
|
81
|
+
```
|
82
|
+
|
83
|
+
Squint only operates on json, jsonb and hstore columns. ActiveRecord
|
84
|
+
will throw a StatementInvalid exception like always if the column type is unsupported by
|
85
|
+
Squint.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
Post.where(title: { not_there: "any value will do" } )
|
89
|
+
```
|
90
|
+
|
91
|
+
```
|
92
|
+
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "title"
|
93
|
+
LINE 1: SELECT COUNT(*) FROM "posts" WHERE "title"."not_there" = 'an...
|
94
|
+
^
|
95
|
+
: SELECT COUNT(*) FROM "posts" WHERE "title"."not_there" = 'any value will do'
|
96
|
+
```
|
97
|
+
|
98
|
+
## Performance
|
99
|
+
To get the most performance out searching jsonb/hstore attributes, add a GIN (preferred) or
|
100
|
+
GIST index to those columns. Find out more
|
101
|
+
[here](https://www.postgresql.org/docs/9.5/static/textsearch-indexes.html)
|
102
|
+
|
103
|
+
TL;DR:
|
104
|
+
|
105
|
+
SQL: 'CREATE INDEX name ON table USING GIN (column);'
|
106
|
+
|
107
|
+
Rails Migration: `add_index(:table, :column_name, using: 'gin')`
|
108
|
+
|
109
|
+
|
110
|
+
## Storext attributes
|
111
|
+
Assuming the database schema above and a model like so:
|
112
|
+
```ruby
|
113
|
+
class Post < ActiveRecord::Base
|
114
|
+
include Storext.model
|
115
|
+
include Squint
|
116
|
+
|
117
|
+
store_attribute :storext_jsonb_attributes, :zip_code, String, default: '90210'
|
118
|
+
store_attribute :storext_jsonb_attributes, :friend_count, Integer, default: 0
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
Example using StoreXT with a default value:
|
123
|
+
```ruby
|
124
|
+
Post.where(storext_jsonb_attributes: { zip_code: '90210' } )
|
125
|
+
# -- jsonb
|
126
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."storext_jsonb_attributes"->>'zip_code' = '90210' OR
|
127
|
+
# (("posts"."storext_jsonb_attributes" ? 'zip_code') IS NULL OR
|
128
|
+
# ("posts"."storext_jsonb_attributes" ? 'zip_code') = FALSE))
|
129
|
+
# -- hstore
|
130
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."storext_hstore_attributes"->'zip_code' = '90210' OR
|
131
|
+
# ((exist("posts"."storext_hstore_attributes", 'zip_code') = FALSE) OR
|
132
|
+
# exist("posts"."storext_hstore_attributes", 'zip_code') IS NULL))
|
133
|
+
#
|
134
|
+
#
|
135
|
+
```
|
136
|
+
If (as in the example above) the default value for the StoreXT attribute is specified, then extra
|
137
|
+
checks for missing column ( `("posts"."storext_jsonb_attributes" ? 'zip_code') IS NULL` ) or
|
138
|
+
missing key ( `("posts"."storext_jsonb_attributes" ? 'zip_code') = FALSE)` ) are added
|
139
|
+
|
140
|
+
When non-default storext values are specified, these extra checks won't be added.
|
141
|
+
|
142
|
+
The Postgres SQL for jsonb and hstore is different. No support for checking for missing `json`
|
143
|
+
columns exists, so don't use those with StoreXT + Squint
|
144
|
+
|
145
|
+
## Developing
|
146
|
+
|
147
|
+
1. Thank you!
|
148
|
+
1. Clone the repository
|
149
|
+
1. `bundle`
|
150
|
+
1. `bundle exec rake --rakefile test/dummy/Rakefile db:setup` # create the db for tests
|
151
|
+
1. `bundle exec rake` # run the tests
|
152
|
+
1. make your changes in a thoughtfully named branch
|
153
|
+
1. ensure good test coverage
|
154
|
+
1. submit a Pull Request
|
155
|
+
|
156
|
+
## Contributors
|
157
|
+
|
158
|
+
Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
|
159
|
+
|
160
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
161
|
+
| [<img src="https://avatars2.githubusercontent.com/u/864581?v=3" width="100px;"/><br /><sub>Kevin Brown</sub>](https://github.com/chevinbrown)<br />[🎨](#design-chevinbrown "Design") [👀](#review-chevinbrown "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/1741179?v=3" width="100px;"/><br /><sub>Andrew Fomera</sub>](http://andrewfomera.com)<br />[👀](#review-king601 "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/1785682?v=3" width="100px;"/><br /><sub>Matthew Jaeh</sub>](https://github.com/Jaehdawg)<br />[🎨](#design-Jaehdawg "Design") [👀](#review-Jaehdawg "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/708692?v=3" width="100px;"/><br /><sub>Ryan T. Hosford</sub>](https://github.com/rthbound)<br />[💻](https://github.com/ProctorU/squint/commits?author=rthbound "Code") | [<img src="https://avatars0.githubusercontent.com/u/3933204?v=3" width="100px;"/><br /><sub>Justin Licata</sub>](https://twitter.com/justinlicata)<br />[💻](https://github.com/ProctorU/squint/commits?author=licatajustin "Code") [🎨](#design-licatajustin "Design") [📖](https://github.com/ProctorU/squint/commits?author=licatajustin "Documentation") [👀](#review-licatajustin "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/97011?v=3" width="100px;"/><br /><sub>David H. Wilkins</sub>](http://conecuh.com)<br />[💬](#question-dwilkins "Answering Questions") [🐛](https://github.com/ProctorU/squint/issues?q=author%3Adwilkins "Bug reports") [💻](https://github.com/ProctorU/squint/commits?author=dwilkins "Code") [🎨](#design-dwilkins "Design") [📖](https://github.com/ProctorU/squint/commits?author=dwilkins "Documentation") [💡](#example-dwilkins "Examples") [👀](#review-dwilkins "Reviewed Pull Requests") [⚠️](https://github.com/ProctorU/squint/commits?author=dwilkins "Tests") | [<img src="https://avatars3.githubusercontent.com/u/19173815?v=3" width="100px;"/><br /><sub>Jay Wright</sub>](https://github.com/TheJayWright)<br />[👀](#review-TheJayWright "Reviewed Pull Requests") |
|
162
|
+
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
163
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
164
|
+
|
165
|
+
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
166
|
+
|
167
|
+
## Credits
|
168
|
+
|
169
|
+
Squint is maintained and funded by [ProctorU](https://twitter.com/ProctorUEng).
|
170
|
+
|
171
|
+
<br>
|
172
|
+
|
173
|
+
<p align="center">
|
174
|
+
<a href="https://twitter.com/ProctorUEng">
|
175
|
+
<img src="https://s3-us-west-2.amazonaws.com/dev-team-resources/procki-eyes.svg" width=108 height=72>
|
176
|
+
</a>
|
177
|
+
|
178
|
+
<h3 align="center">
|
179
|
+
<a href="https://twitter.com/ProctorUEng">ProctorU Engineering & Design</a>
|
180
|
+
</h3>
|
181
|
+
|
182
|
+
<p align="center">
|
183
|
+
A simple online proctoring service that allows you to take exams or certification tests at home.
|
184
|
+
</p>
|
185
|
+
</p>
|