activerecord-cockroachdb-adapter 5.2.2 → 6.0.0beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -291
- data/activerecord-cockroachdb-adapter.gemspec +2 -3
- data/build/teamcity-test.sh +2 -2
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +1 -80
- data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +0 -9
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +22 -71
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +11 -124
- metadata +10 -30
- data/lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb +0 -27
- data/lib/active_record/connection_adapters/cockroachdb/column_methods.rb +0 -53
- data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +0 -121
- data/lib/active_record/connection_adapters/cockroachdb/setup.rb +0 -19
- data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +0 -44
- data/lib/active_record/connection_adapters/cockroachdb/table_definition.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a580dde40d074b1982c6642310513d79bbcb710032a476a64bb9fcb99bec72bb
|
4
|
+
data.tar.gz: 91b11b334235484e298704ae514d435a61451a02f3f759881141fd6354549ee4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77b98dff7d2069ba0cdcd78bccf5aedf9ac31192c0813d7893a5a6f1084537d9d0b03039be58fe83a5a0e0cd58d5d42ba567e43beee62507d8212242722fdb13
|
7
|
+
data.tar.gz: f1bf6e2fa1925d94133a98dcd03690a24da2631134cc6a5eb6dd860253392f398db88572942f409f13dec4d3d7aeb34f18317a5b8cd4b30c70e4d75d13599b47
|
data/README.md
CHANGED
@@ -22,297 +22,6 @@ development:
|
|
22
22
|
user: <username>
|
23
23
|
```
|
24
24
|
|
25
|
-
## Working with Spatial Data
|
26
|
-
|
27
|
-
The adapter uses [RGeo](https://github.com/rgeo/rgeo) and [RGeo-ActiveRecord](https://github.com/rgeo/rgeo-activerecord) to represent geometric and geographic data as Ruby objects and easily interface them with the adapter. The following is a brief introduction to RGeo and tips to help setup your spatial application. More documentation about RGeo can be found in the [YARD Docs](https://rubydoc.info/github/rgeo/rgeo) and [wiki](https://github.com/rgeo/rgeo/wiki).
|
28
|
-
|
29
|
-
### Installing RGeo
|
30
|
-
|
31
|
-
RGeo can be installed with the following command:
|
32
|
-
|
33
|
-
```sh
|
34
|
-
gem install rgeo
|
35
|
-
```
|
36
|
-
|
37
|
-
The best way to use RGeo is with GEOS support. If you have a version of libgeos installed, you can check that it was properly linked with RGeo by running the following commands:
|
38
|
-
|
39
|
-
```rb
|
40
|
-
require 'rgeo'
|
41
|
-
|
42
|
-
RGeo::Geos.supported?
|
43
|
-
#=> true
|
44
|
-
```
|
45
|
-
|
46
|
-
If this is `false`, you may need to specify the GEOS directory while installing. Here's an example linking it to the CockroachDB GEOS binary.
|
47
|
-
|
48
|
-
```sh
|
49
|
-
gem install rgeo -- --with-geos-dir=/path/to/cockroach/lib/
|
50
|
-
```
|
51
|
-
|
52
|
-
### Working with RGeo
|
53
|
-
|
54
|
-
RGeo uses [factories](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)) to create geometry objects and define their properties. Different factories define their own implementations for standard methods. For instance, the `RGeo::Geographic.spherical_factory` accepts latitudes and longitues as its coordinates and does computations on a spherical surface, while `RGeo::Cartesian.factory` implements geometry objects on a plane.
|
55
|
-
|
56
|
-
The factory (or factories) you choose to use will depend on the requirements of your application and what you need to do with the geometries they produce. For example, if you are working with points or other simple geometries across long distances and need precise results, the spherical factory is a good choice. If you're working with polygons or multipolygons and analyzing complex relationships between them (`intersects?`, `difference`, etc.), then using a cartesian factory backed by GEOS is a much better option.
|
57
|
-
|
58
|
-
Once you've selected a factory, you need to create objects. RGeo supports geometry creation through standard constructors (`point`, `line_string`, `polygon`, etc.) or by WKT and WKB.
|
59
|
-
|
60
|
-
```rb
|
61
|
-
require 'rgeo'
|
62
|
-
factory = RGeo::Cartesian.factory(srid: 3857)
|
63
|
-
|
64
|
-
# Create a line_string from points
|
65
|
-
pt1 = factory.point(0,0)
|
66
|
-
pt2 = factory.point(1,1)
|
67
|
-
pt3 = factory.point(2,2)
|
68
|
-
line_string = factory.line_string([pt1,pt2,pt3])
|
69
|
-
|
70
|
-
p line_string.length
|
71
|
-
#=> 2.8284271247461903
|
72
|
-
|
73
|
-
# check line_string equality
|
74
|
-
line_string2 = factory.parse_wkt("LINESTRING (0 0, 1 1, 2 2)")
|
75
|
-
p line_string == line_string2
|
76
|
-
#=> true
|
77
|
-
|
78
|
-
# create polygon and test intersection with line_string
|
79
|
-
pt4 = factory.point(0,2)
|
80
|
-
outer_ring = factory.linear_ring([pt1,pt2,pt3,pt4,pt1])
|
81
|
-
poly = factory.polygon(outer_ring)
|
82
|
-
|
83
|
-
p line_string.intersects? poly
|
84
|
-
#=> true
|
85
|
-
```
|
86
|
-
### Creating Spatial Tables
|
87
|
-
|
88
|
-
To store spatial data, you must create a column with a spatial type. PostGIS
|
89
|
-
provides a variety of spatial types, including point, linestring, polygon, and
|
90
|
-
different kinds of collections. These types are defined in a standard produced
|
91
|
-
by the Open Geospatial Consortium. You can specify options indicating the coordinate system and number of coordinates for the values you are storing.
|
92
|
-
|
93
|
-
The adapter extends ActiveRecord's migration syntax to
|
94
|
-
support these spatial types. The following example creates five spatial
|
95
|
-
columns in a table:
|
96
|
-
|
97
|
-
```rb
|
98
|
-
create_table :my_spatial_table do |t|
|
99
|
-
t.column :shape1, :geometry
|
100
|
-
t.geometry :shape2
|
101
|
-
t.line_string :path, srid: 3857
|
102
|
-
t.st_point :lonlat, geographic: true
|
103
|
-
t.st_point :lonlatheight, geographic: true, has_z: true
|
104
|
-
end
|
105
|
-
```
|
106
|
-
|
107
|
-
The first column, "shape1", is created with type "geometry". This is a general
|
108
|
-
"base class" for spatial types; the column declares that it can contain values
|
109
|
-
of _any_ spatial type.
|
110
|
-
|
111
|
-
The second column, "shape2", uses a shorthand syntax for the same type as the shape1 column.
|
112
|
-
You can create a column either by invoking `column` or invoking the name of the type directly.
|
113
|
-
|
114
|
-
The third column, "path", has a specific geometric type, `line_string`. It
|
115
|
-
also specifies an SRID (spatial reference ID) that indicates which coordinate
|
116
|
-
system it expects the data to be in. The column now has a "constraint" on it;
|
117
|
-
it will accept only LineString data, and only data whose SRID is 3857.
|
118
|
-
|
119
|
-
The fourth column, "lonlat", has the `st_point` type, and accepts only Point
|
120
|
-
data. Furthermore, it declares the column as "geographic", which means it
|
121
|
-
accepts longitude/latitude data, and performs calculations such as distances
|
122
|
-
using a spheroidal domain.
|
123
|
-
|
124
|
-
The fifth column, "lonlatheight", is a geographic (longitude/latitude) point
|
125
|
-
that also includes a third "z" coordinate that can be used to store height
|
126
|
-
information.
|
127
|
-
|
128
|
-
The following are the data types understood by PostGIS and exposed by
|
129
|
-
the adapter:
|
130
|
-
|
131
|
-
- `:geometry` -- Any geometric type
|
132
|
-
- `:st_point` -- Point data
|
133
|
-
- `:line_string` -- LineString data
|
134
|
-
- `:st_polygon` -- Polygon data
|
135
|
-
- `:geometry_collection` -- Any collection type
|
136
|
-
- `:multi_point` -- A collection of Points
|
137
|
-
- `:multi_line_string` -- A collection of LineStrings
|
138
|
-
- `:multi_polygon` -- A collection of Polygons
|
139
|
-
|
140
|
-
Following are the options understood by the adapter:
|
141
|
-
|
142
|
-
- `:geographic` -- If set to true, create a PostGIS geography column for
|
143
|
-
longitude/latitude data over a spheroidal domain; otherwise create a
|
144
|
-
geometry column in a flat coordinate system. Default is false. Also
|
145
|
-
implies :srid set to 4326.
|
146
|
-
- `:srid` -- Set a SRID constraint for the column. Default is 4326 for a
|
147
|
-
geography column, or 0 for a geometry column. Note that PostGIS currently
|
148
|
-
(as of version 2.0) requires geography columns to have SRID 4326, so this
|
149
|
-
constraint is of limited use for geography columns.
|
150
|
-
- `:has_z` -- Specify that objects in this column include a Z coordinate.
|
151
|
-
Default is false.
|
152
|
-
- `:has_m` -- Specify that objects in this column include an M coordinate.
|
153
|
-
Default is false.
|
154
|
-
|
155
|
-
To create a PostGIS spatial index, add `using: :gist` to your index:
|
156
|
-
|
157
|
-
```rb
|
158
|
-
add_index :my_table, :lonlat, using: :gist
|
159
|
-
|
160
|
-
# or
|
161
|
-
|
162
|
-
change_table :my_table do |t|
|
163
|
-
t.index :lonlat, using: :gist
|
164
|
-
end
|
165
|
-
```
|
166
|
-
### Configuring ActiveRecord
|
167
|
-
|
168
|
-
ActiveRecord's usefulness stems from the way it automatically configures
|
169
|
-
classes based on the database structure and schema. If a column in the
|
170
|
-
database has an integer type, ActiveRecord automatically casts the data to a
|
171
|
-
Ruby Integer. In the same way, the adapter automatically
|
172
|
-
casts spatial data to a corresponding RGeo data type.
|
173
|
-
|
174
|
-
RGeo offers more flexibility in its type system than can be
|
175
|
-
interpreted solely from analyzing the database column. For example, you can
|
176
|
-
configure RGeo objects to exhibit certain behaviors related to their
|
177
|
-
serialization, validation, coordinate system, or computation. These settings
|
178
|
-
are embodied in the RGeo factory associated with the object.
|
179
|
-
|
180
|
-
You can configure the adapter to use a particular factory (i.e. a
|
181
|
-
particular combination of settings) for data associated with each type in
|
182
|
-
the database.
|
183
|
-
|
184
|
-
Here's an example using a Geos default factory:
|
185
|
-
|
186
|
-
```ruby
|
187
|
-
RGeo::ActiveRecord::SpatialFactoryStore.instance.tap do |config|
|
188
|
-
# By default, use the GEOS implementation for spatial columns.
|
189
|
-
config.default = RGeo::Geos.factory_generator
|
190
|
-
|
191
|
-
# But use a geographic implementation for point columns.
|
192
|
-
config.register(RGeo::Geographic.spherical_factory(srid: 4326), geo_type: "point")
|
193
|
-
end
|
194
|
-
```
|
195
|
-
|
196
|
-
The default spatial factory for geographic columns is `RGeo::Geographic.spherical_factory`.
|
197
|
-
The default spatial factory for cartesian columns is `RGeo::Cartesian.preferred_factory`.
|
198
|
-
You do not need to configure the `SpatialFactoryStore` if these defaults are ok.
|
199
|
-
|
200
|
-
More information about configuration options for the `SpatialFactoryStore` can be found in the [rgeo-activerecord](https://github.com/rgeo/rgeo-activerecord#spatial-factories-for-columns) docs.
|
201
|
-
|
202
|
-
### Reading and Writing Spatial Columns
|
203
|
-
|
204
|
-
When you access a spatial attribute on your ActiveRecord model, it is given to
|
205
|
-
you as an RGeo geometry object (or nil, for attributes that allow null
|
206
|
-
values). You can then call the RGeo api on the object. For example, consider
|
207
|
-
the MySpatialTable class we worked with above:
|
208
|
-
|
209
|
-
```rb
|
210
|
-
record = MySpatialTable.find(1)
|
211
|
-
point = record.lonlat # Returns an RGeo::Feature::Point
|
212
|
-
p point.x # displays the x coordinate
|
213
|
-
p point.geometry_type.type_name # displays "Point"
|
214
|
-
```
|
215
|
-
|
216
|
-
The RGeo factory for the value is determined by how you configured the
|
217
|
-
ActiveRecord class, as described above. In this case, we explicitly set a
|
218
|
-
spherical factory for the `:lonlat` column:
|
219
|
-
|
220
|
-
```rb
|
221
|
-
factory = point.factory # returns a spherical factory
|
222
|
-
```
|
223
|
-
|
224
|
-
You can set a spatial attribute by providing an RGeo geometry object, or by
|
225
|
-
providing the WKT string representation of the geometry. If a string is
|
226
|
-
provided, the adapter will attempt to parse it as WKT and
|
227
|
-
set the value accordingly.
|
228
|
-
|
229
|
-
```rb
|
230
|
-
record.lonlat = 'POINT(-122 47)' # sets the value to the given point
|
231
|
-
```
|
232
|
-
|
233
|
-
If the WKT parsing fails, the value currently will be silently set to nil. In
|
234
|
-
the future, however, this will raise an exception.
|
235
|
-
|
236
|
-
```rb
|
237
|
-
record.lonlat = 'POINT(x)' # sets the value to nil
|
238
|
-
```
|
239
|
-
|
240
|
-
If you set the value to an RGeo object, the factory needs to match the factory
|
241
|
-
for the attribute. If the factories do not match, the adapter
|
242
|
-
will attempt to cast the value to the correct factory.
|
243
|
-
|
244
|
-
```rb
|
245
|
-
p2 = factory.point(-122, 47) # p2 is a point in a spherical factory
|
246
|
-
record.lonlat = p2 # sets the value to the given point
|
247
|
-
record.shape1 = p2 # shape1 uses a flat geos factory, so it
|
248
|
-
# will cast p2 into that coordinate system
|
249
|
-
# before setting the value
|
250
|
-
record.save
|
251
|
-
```
|
252
|
-
|
253
|
-
If you attempt to set the value to the wrong type, such as setting a linestring attribute to a point value, you will get an exception from the database when you attempt to save the record.
|
254
|
-
|
255
|
-
```rb
|
256
|
-
record.path = p2 # This will appear to work, but...
|
257
|
-
record.save # This will raise an exception from the database
|
258
|
-
```
|
259
|
-
|
260
|
-
### Spatial Queries
|
261
|
-
|
262
|
-
You can create simple queries based on representational equality in the same
|
263
|
-
way you would on a scalar column:
|
264
|
-
|
265
|
-
```ruby
|
266
|
-
record2 = MySpatialTable.where(:lonlat => factory.point(-122, 47)).first
|
267
|
-
```
|
268
|
-
|
269
|
-
You can also use WKT:
|
270
|
-
|
271
|
-
```ruby
|
272
|
-
record3 = MySpatialTable.where(:lonlat => 'POINT(-122 47)').first
|
273
|
-
```
|
274
|
-
|
275
|
-
Note that these queries use representational equality, meaning they return
|
276
|
-
records where the lonlat value matches the given value exactly. A 0.00001
|
277
|
-
degree difference would not match, nor would a different representation of the
|
278
|
-
same geometry (like a multi_point with a single element). Equality queries
|
279
|
-
aren't generally all that useful in real world applications. Typically, if you
|
280
|
-
want to perform a spatial query, you'll look for, say, all the points within a
|
281
|
-
given area. For those queries, you'll need to use the standard spatial SQL
|
282
|
-
functions provided by PostGIS.
|
283
|
-
|
284
|
-
To perform more advanced spatial queries, you can use the extended Arel interface included in the adapter. The functions accept WKT strings or RGeo features.
|
285
|
-
|
286
|
-
```rb
|
287
|
-
point = RGeo::Geos.factory(srid: 0).point(1,1)
|
288
|
-
|
289
|
-
# Example Building model where geom is a column of polygons.
|
290
|
-
buildings = Building.arel_table
|
291
|
-
containing_buiildings = Building.where(buildings[:geom].st_contains(point))
|
292
|
-
```
|
293
|
-
|
294
|
-
See the [rgeo-activerecord YARD Docs](https://rubydoc.info/github/rgeo/rgeo-activerecord/RGeo/ActiveRecord/SpatialExpressions) for a list of available PostGIS functions.
|
295
|
-
|
296
|
-
### Validation Issues
|
297
|
-
|
298
|
-
If you see an `RGeo::Error::InvalidGeometry (LinearRing failed ring test)` message while loading data or creating geometries, this means that the geometry you are trying to instantiate is not topologically valid. This is usually due to self-intersections in the geometry. The default behavior of RGeo factories is to raise this error when an invalid geometry is being instansiated, but this can be ignored by setting the `uses_lenient_assertions` flag to `true` when creating your factory.
|
299
|
-
|
300
|
-
```rb
|
301
|
-
regular_fac = RGeo::Geographic.spherical_factory
|
302
|
-
modified_fac = RGeo::Geographic.spherical_factory(uses_lenient_assertions: true)
|
303
|
-
|
304
|
-
wkt = "POLYGON (0 0, 1 1, 0 1, 1 0, 0 0)" # closed ring with self intersection
|
305
|
-
|
306
|
-
regular_fac.parse_wkt(wkt)
|
307
|
-
#=> RGeo::Error::InvalidGeometry (LinearRing failed ring test)
|
308
|
-
|
309
|
-
p modified_fac.parse_wkt(wkt)
|
310
|
-
#=> #<RGeo::Geographic::SphericalPolygonImpl>
|
311
|
-
```
|
312
|
-
|
313
|
-
Be careful when performing calculations on potentially invalid geometries, as the results might be nonsensical. For example, the area returned of an hourglass made of 2 equivalent triangles with a self-intersection in the middle is 0.
|
314
|
-
|
315
|
-
Note that when using the `spherical_factory`, there is a chance that valid geometries will be interpreted as invalid due to floating point issues with small geometries.
|
316
25
|
|
317
26
|
## Modifying the adapter?
|
318
27
|
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "activerecord-cockroachdb-adapter"
|
7
|
-
spec.version = "
|
7
|
+
spec.version = "6.0.0beta1"
|
8
8
|
spec.licenses = ["Apache-2.0"]
|
9
9
|
spec.authors = ["Cockroach Labs"]
|
10
10
|
spec.email = ["cockroach-db@googlegroups.com"]
|
@@ -13,9 +13,8 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
|
14
14
|
spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
|
15
15
|
|
16
|
-
spec.add_dependency "activerecord", "~>
|
16
|
+
spec.add_dependency "activerecord", "~> 6.0.3"
|
17
17
|
spec.add_dependency "pg", ">= 0.20"
|
18
|
-
spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
|
19
18
|
|
20
19
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
21
20
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
data/build/teamcity-test.sh
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
set -euox pipefail
|
4
4
|
|
5
5
|
# Download CockroachDB
|
6
|
-
VERSION=v20.2.
|
6
|
+
VERSION=v20.2.1
|
7
7
|
wget -qO- https://binaries.cockroachdb.com/cockroach-$VERSION.linux-amd64.tgz | tar xvz
|
8
8
|
readonly COCKROACH=./cockroach-$VERSION.linux-amd64/cockroach
|
9
9
|
|
@@ -21,7 +21,7 @@ run_cockroach() {
|
|
21
21
|
cockroach quit --insecure || true
|
22
22
|
rm -rf cockroach-data
|
23
23
|
# Start CockroachDB.
|
24
|
-
cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --
|
24
|
+
cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
|
25
25
|
# Ensure CockroachDB is stopped on script exit.
|
26
26
|
trap "echo 'Exit routine: Killing CockroachDB.' && kill -9 $! &> /dev/null" EXIT
|
27
27
|
# Wait until CockroachDB has started.
|
@@ -2,87 +2,8 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module CockroachDB
|
4
4
|
module PostgreSQLColumnMonkeyPatch
|
5
|
-
# most functions taken from activerecord-postgis-adapter spatial_column
|
6
|
-
# https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb
|
7
|
-
def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil,
|
8
|
-
default_function = nil, collation = nil, comment = nil, cast_type = nil, opts = nil)
|
9
|
-
@sql_type_metadata = sql_type_metadata
|
10
|
-
@geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
|
11
|
-
|
12
|
-
@cast_type = cast_type
|
13
|
-
@geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
|
14
|
-
if opts
|
15
|
-
# This case comes from an entry in the geometry_columns table
|
16
|
-
set_geometric_type_from_name(opts[:type])
|
17
|
-
@srid = opts[:srid].to_i
|
18
|
-
@has_z = !!opts[:has_z]
|
19
|
-
@has_m = !!opts[:has_m]
|
20
|
-
elsif @geographic
|
21
|
-
# Geographic type information is embedded in the SQL type
|
22
|
-
@srid = 4326
|
23
|
-
@has_z = @has_m = false
|
24
|
-
build_from_sql_type(sql_type_metadata.sql_type)
|
25
|
-
elsif sql_type =~ /geography|geometry|point|linestring|polygon/i
|
26
|
-
build_from_sql_type(sql_type_metadata.sql_type)
|
27
|
-
elsif sql_type_metadata.sql_type =~ /geography|geometry|point|linestring|polygon/i
|
28
|
-
# A geometry column with no geometry_columns entry.
|
29
|
-
# @geometric_type = geo_type_from_sql_type(sql_type)
|
30
|
-
build_from_sql_type(sql_type_metadata.sql_type)
|
31
|
-
end
|
32
|
-
super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
|
33
|
-
if spatial?
|
34
|
-
if @srid
|
35
|
-
@limit = { srid: @srid, type: to_type_name(geometric_type) }
|
36
|
-
@limit[:has_z] = true if @has_z
|
37
|
-
@limit[:has_m] = true if @has_m
|
38
|
-
@limit[:geographic] = true if @geographic
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
attr_reader :geographic,
|
44
|
-
:geometric_type,
|
45
|
-
:has_m,
|
46
|
-
:has_z,
|
47
|
-
:srid
|
48
|
-
|
49
|
-
alias geographic? geographic
|
50
|
-
alias has_z? has_z
|
51
|
-
alias has_m? has_m
|
52
|
-
|
53
|
-
def limit
|
54
|
-
spatial? ? @limit : super
|
55
|
-
end
|
56
|
-
|
57
|
-
def spatial?
|
58
|
-
%i[geometry geography].include?(@sql_type_metadata.type)
|
59
|
-
end
|
60
|
-
|
61
5
|
def serial?
|
62
|
-
default_function ==
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def set_geometric_type_from_name(name)
|
68
|
-
@geometric_type = RGeo::ActiveRecord.geometric_type_from_name(name) || RGeo::Feature::Geometry
|
69
|
-
end
|
70
|
-
|
71
|
-
def build_from_sql_type(sql_type)
|
72
|
-
geo_type, @srid, @has_z, @has_m = OID::Spatial.parse_sql_type(sql_type)
|
73
|
-
set_geometric_type_from_name(geo_type)
|
74
|
-
end
|
75
|
-
|
76
|
-
def to_type_name(geometric_type)
|
77
|
-
name = geometric_type.type_name.underscore
|
78
|
-
case name
|
79
|
-
when 'point'
|
80
|
-
'st_point'
|
81
|
-
when 'polygon'
|
82
|
-
'st_polygon'
|
83
|
-
else
|
84
|
-
name
|
85
|
-
end
|
6
|
+
default_function == "unique_rowid()"
|
86
7
|
end
|
87
8
|
end
|
88
9
|
end
|
@@ -14,19 +14,10 @@ module ActiveRecord
|
|
14
14
|
# always be strings. Then, we won't have to make any additional changes
|
15
15
|
# to ActiveRecord to support inserting integer values into string
|
16
16
|
# columns.
|
17
|
-
#
|
18
|
-
# For spatial types, data is stored as Well-known Binary (WKB) strings
|
19
|
-
# (https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary)
|
20
|
-
# but when creating objects, using RGeo features is more convenient than
|
21
|
-
# converting to WKB, so this does it automatically.
|
22
17
|
def _quote(value)
|
23
18
|
case value
|
24
19
|
when Numeric
|
25
20
|
"'#{quote_string(value.to_s)}'"
|
26
|
-
when RGeo::Feature::Geometry
|
27
|
-
"'#{RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true).generate(value)}'"
|
28
|
-
when RGeo::Cartesian::BoundingBox
|
29
|
-
"'#{value.min_x},#{value.min_y},#{value.max_x},#{value.max_y}'::box"
|
30
21
|
else
|
31
22
|
super
|
32
23
|
end
|
@@ -39,52 +39,8 @@ module ActiveRecord
|
|
39
39
|
nil
|
40
40
|
end
|
41
41
|
|
42
|
-
def columns(table_name)
|
43
|
-
# Limit, precision, and scale are all handled by the superclass.
|
44
|
-
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
|
45
|
-
oid = oid.to_i
|
46
|
-
fmod = fmod.to_i
|
47
|
-
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
|
48
|
-
cast_type = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
|
49
|
-
default_value = extract_value_from_default(default)
|
50
|
-
|
51
|
-
default_function = extract_default_function(default_value, default)
|
52
|
-
new_column(table_name, column_name, default_value, cast_type, type_metadata, !notnull,
|
53
|
-
default_function, collation, comment)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def new_column(table_name, column_name, default, cast_type, sql_type_metadata = nil,
|
58
|
-
null = true, default_function = nil, collation = nil, comment = nil)
|
59
|
-
# JDBC gets true/false in Rails 4, where other platforms get 't'/'f' strings.
|
60
|
-
if null.is_a?(String)
|
61
|
-
null = (null == "t")
|
62
|
-
end
|
63
|
-
|
64
|
-
column_info = spatial_column_info(table_name).get(column_name, sql_type_metadata.sql_type)
|
65
|
-
|
66
|
-
PostgreSQLColumn.new(
|
67
|
-
column_name,
|
68
|
-
default,
|
69
|
-
sql_type_metadata,
|
70
|
-
null,
|
71
|
-
table_name,
|
72
|
-
default_function,
|
73
|
-
collation,
|
74
|
-
comment,
|
75
|
-
cast_type,
|
76
|
-
column_info
|
77
|
-
)
|
78
|
-
end
|
79
|
-
|
80
42
|
# CockroachDB will use INT8 if the SQL type is INTEGER, so we make it use
|
81
43
|
# INT4 explicitly when needed.
|
82
|
-
#
|
83
|
-
# For spatial columns, include the limit to properly format the column name
|
84
|
-
# since type alone is not enough to format the column.
|
85
|
-
# Ex. type_to_sql(:geography, limit: "Point,4326")
|
86
|
-
# => "geography(Point,4326)"
|
87
|
-
#
|
88
44
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
89
45
|
sql = \
|
90
46
|
case type.to_s
|
@@ -96,44 +52,39 @@ module ActiveRecord
|
|
96
52
|
when 5..8; "int8"
|
97
53
|
else super
|
98
54
|
end
|
99
|
-
when "geometry", "geography"
|
100
|
-
"#{type}(#{limit})"
|
101
55
|
else
|
102
56
|
super
|
103
57
|
end
|
104
58
|
# The call to super might have appeneded [] already.
|
105
59
|
if array && type != :primary_key && !sql.end_with?("[]")
|
106
|
-
sql = "#{sql}[]"
|
60
|
+
sql = "#{sql}[]"
|
107
61
|
end
|
108
62
|
sql
|
109
63
|
end
|
110
64
|
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
geometry: { name: "geometry" },
|
117
|
-
geometry_collection: { name: "geometry_collection" },
|
118
|
-
line_string: { name: "line_string" },
|
119
|
-
multi_line_string: { name: "multi_line_string" },
|
120
|
-
multi_point: { name: "multi_point" },
|
121
|
-
multi_polygon: { name: "multi_polygon" },
|
122
|
-
spatial: { name: "geometry" },
|
123
|
-
st_point: { name: "st_point" },
|
124
|
-
st_polygon: { name: "st_polygon" }
|
125
|
-
)
|
126
|
-
end
|
65
|
+
# This overrides the method from PostegreSQL adapter
|
66
|
+
# Resets the sequence of a table's primary key to the maximum value.
|
67
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
68
|
+
unless pk && sequence
|
69
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
127
70
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
71
|
+
pk ||= default_pk
|
72
|
+
sequence ||= default_sequence
|
73
|
+
end
|
74
|
+
|
75
|
+
if @logger && pk && !sequence
|
76
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
77
|
+
end
|
78
|
+
|
79
|
+
if pk && sequence
|
80
|
+
quoted_sequence = quote_table_name(sequence)
|
81
|
+
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
82
|
+
if max_pk.nil?
|
83
|
+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
84
|
+
end
|
132
85
|
|
133
|
-
|
134
|
-
|
135
|
-
@spatial_column_info ||= {}
|
136
|
-
@spatial_column_info[table_name.to_sym] ||= SpatialColumnInfo.new(self, table_name.to_s)
|
86
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
87
|
+
end
|
137
88
|
end
|
138
89
|
end
|
139
90
|
end
|
@@ -1,24 +1,12 @@
|
|
1
|
-
require "rgeo/active_record"
|
2
|
-
|
3
1
|
require 'active_record/connection_adapters/postgresql_adapter'
|
4
|
-
require "active_record/connection_adapters/cockroachdb/column_methods"
|
5
2
|
require "active_record/connection_adapters/cockroachdb/schema_statements"
|
6
3
|
require "active_record/connection_adapters/cockroachdb/referential_integrity"
|
7
4
|
require "active_record/connection_adapters/cockroachdb/transaction_manager"
|
5
|
+
require "active_record/connection_adapters/cockroachdb/column"
|
8
6
|
require "active_record/connection_adapters/cockroachdb/database_statements"
|
9
|
-
require "active_record/connection_adapters/cockroachdb/table_definition"
|
10
7
|
require "active_record/connection_adapters/cockroachdb/quoting"
|
11
8
|
require "active_record/connection_adapters/cockroachdb/type"
|
12
9
|
require "active_record/connection_adapters/cockroachdb/attribute_methods"
|
13
|
-
require "active_record/connection_adapters/cockroachdb/column"
|
14
|
-
require "active_record/connection_adapters/cockroachdb/spatial_column_info"
|
15
|
-
require "active_record/connection_adapters/cockroachdb/setup"
|
16
|
-
require "active_record/connection_adapters/cockroachdb/oid/spatial"
|
17
|
-
require "active_record/connection_adapters/cockroachdb/arel_tosql"
|
18
|
-
|
19
|
-
# Run to ignore spatial tables that will break schemna dumper.
|
20
|
-
# Defined in ./setup.rb
|
21
|
-
ActiveRecord::ConnectionAdapters::CockroachDB.initial_setup
|
22
10
|
|
23
11
|
module ActiveRecord
|
24
12
|
module ConnectionHandling
|
@@ -39,7 +27,14 @@ module ActiveRecord
|
|
39
27
|
# The postgres drivers don't allow the creation of an unconnected
|
40
28
|
# PG::Connection object, so just pass a nil connection object for the
|
41
29
|
# time being.
|
42
|
-
|
30
|
+
conn = PG.connect(conn_params)
|
31
|
+
ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
|
32
|
+
rescue ::PG::Error => error
|
33
|
+
if error.message.include?("does not exist")
|
34
|
+
raise ActiveRecord::NoDatabaseError
|
35
|
+
else
|
36
|
+
raise
|
37
|
+
end
|
43
38
|
end
|
44
39
|
end
|
45
40
|
end
|
@@ -50,97 +45,11 @@ module ActiveRecord
|
|
50
45
|
ADAPTER_NAME = "CockroachDB".freeze
|
51
46
|
DEFAULT_PRIMARY_KEY = "rowid"
|
52
47
|
|
53
|
-
SPATIAL_COLUMN_OPTIONS =
|
54
|
-
{
|
55
|
-
geography: { geographic: true },
|
56
|
-
geometry: {},
|
57
|
-
geometry_collection: {},
|
58
|
-
line_string: {},
|
59
|
-
multi_line_string: {},
|
60
|
-
multi_point: {},
|
61
|
-
multi_polygon: {},
|
62
|
-
spatial: {},
|
63
|
-
st_point: {},
|
64
|
-
st_polygon: {},
|
65
|
-
}
|
66
|
-
|
67
|
-
# http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
|
68
|
-
DEFAULT_SRID = 0
|
69
|
-
|
70
48
|
include CockroachDB::SchemaStatements
|
71
49
|
include CockroachDB::ReferentialIntegrity
|
72
50
|
include CockroachDB::DatabaseStatements
|
73
51
|
include CockroachDB::Quoting
|
74
52
|
|
75
|
-
# override
|
76
|
-
# This method makes a sql query to gather information about columns
|
77
|
-
# in a table. It returns an array of arrays (one for each col) and
|
78
|
-
# is mapped to columns in the SchemaStatements#columns method.
|
79
|
-
#
|
80
|
-
# The issue with the default method is that the sql_type field is
|
81
|
-
# retrieved with the `format_type` function, but this is implemented
|
82
|
-
# differently in CockroachDB than PostGIS, so geometry/geography
|
83
|
-
# types are missing information which makes parsing them impossible.
|
84
|
-
# Below is an example of what `format_type` returns for a geometry
|
85
|
-
# column.
|
86
|
-
#
|
87
|
-
# column_type: geometry(POINT, 4326)
|
88
|
-
# Expected: geometry(POINT, 4326)
|
89
|
-
# Actual: geometry
|
90
|
-
#
|
91
|
-
# The solution is to make the default query with super, then
|
92
|
-
# iterate through the columns and if it is a spatial type,
|
93
|
-
# access the proper column_type with the information_schema.columns
|
94
|
-
# table.
|
95
|
-
#
|
96
|
-
# @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
|
97
|
-
def column_definitions(table_name)
|
98
|
-
fields = super
|
99
|
-
# iterate through and identify all spatial fields based on format_type
|
100
|
-
# being geometry or geography, then query for the information_schema.column
|
101
|
-
# column_type because that contains the necessary information.
|
102
|
-
fields.map do |field|
|
103
|
-
dtype = field[1]
|
104
|
-
if dtype == 'geometry' || dtype == 'geography'
|
105
|
-
col_name = field[0]
|
106
|
-
data_type = \
|
107
|
-
query(<<~SQL, "SCHEMA")
|
108
|
-
SELECT c.data_type
|
109
|
-
FROM information_schema.columns c
|
110
|
-
WHERE c.table_name = #{quote(table_name)}
|
111
|
-
AND c.column_name = #{quote(col_name)}
|
112
|
-
SQL
|
113
|
-
field[1] = data_type[0][0]
|
114
|
-
end
|
115
|
-
field
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def arel_visitor
|
120
|
-
Arel::Visitors::CockroachDB.new(self)
|
121
|
-
end
|
122
|
-
|
123
|
-
def self.spatial_column_options(key)
|
124
|
-
SPATIAL_COLUMN_OPTIONS[key]
|
125
|
-
end
|
126
|
-
|
127
|
-
def postgis_lib_version
|
128
|
-
@postgis_lib_version ||= select_value("SELECT PostGIS_Lib_Version()")
|
129
|
-
end
|
130
|
-
|
131
|
-
def default_srid
|
132
|
-
DEFAULT_SRID
|
133
|
-
end
|
134
|
-
|
135
|
-
def srs_database_columns
|
136
|
-
{
|
137
|
-
auth_name_column: "auth_name",
|
138
|
-
auth_srid_column: "auth_srid",
|
139
|
-
proj4text_column: "proj4text",
|
140
|
-
srtext_column: "srtext",
|
141
|
-
}
|
142
|
-
end
|
143
|
-
|
144
53
|
def debugging?
|
145
54
|
!!ENV["DEBUG_COCKROACHDB_ADAPTER"]
|
146
55
|
end
|
@@ -171,11 +80,6 @@ module ActiveRecord
|
|
171
80
|
false
|
172
81
|
end
|
173
82
|
|
174
|
-
def supports_ranges?
|
175
|
-
# See cockroachdb/cockroach#17022
|
176
|
-
false
|
177
|
-
end
|
178
|
-
|
179
83
|
def supports_materialized_views?
|
180
84
|
false
|
181
85
|
end
|
@@ -249,26 +153,11 @@ module ActiveRecord
|
|
249
153
|
end
|
250
154
|
@crdb_version = version_num
|
251
155
|
end
|
252
|
-
|
156
|
+
|
253
157
|
private
|
254
158
|
|
255
159
|
def initialize_type_map(m = type_map)
|
256
|
-
|
257
|
-
geography
|
258
|
-
geometry
|
259
|
-
geometry_collection
|
260
|
-
line_string
|
261
|
-
multi_line_string
|
262
|
-
multi_point
|
263
|
-
multi_polygon
|
264
|
-
st_point
|
265
|
-
st_polygon
|
266
|
-
).each do |geo_type|
|
267
|
-
m.register_type(geo_type) do |oid, _, sql_type|
|
268
|
-
CockroachDB::OID::Spatial.new(oid, sql_type)
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
160
|
+
super(m)
|
272
161
|
# NOTE(joey): PostgreSQL intervals have a precision.
|
273
162
|
# CockroachDB intervals do not, so overide the type
|
274
163
|
# definition. Returning a ArgumentError may not be correct.
|
@@ -280,8 +169,6 @@ module ActiveRecord
|
|
280
169
|
end
|
281
170
|
OID::SpecializedString.new(:interval, precision: precision)
|
282
171
|
end
|
283
|
-
|
284
|
-
super(m)
|
285
172
|
end
|
286
173
|
|
287
174
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-cockroachdb-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cockroach Labs
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.0.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 6.0.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.20'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rgeo-activerecord
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 7.0.0
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 7.0.0
|
55
41
|
description: Allows the use of CockroachDB as a backend for ActiveRecord and Rails
|
56
42
|
apps.
|
57
43
|
email:
|
@@ -75,19 +61,13 @@ files:
|
|
75
61
|
- build/local-test.sh
|
76
62
|
- build/teamcity-test.sh
|
77
63
|
- docker.sh
|
78
|
-
- lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb
|
79
64
|
- lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb
|
80
65
|
- lib/active_record/connection_adapters/cockroachdb/column.rb
|
81
|
-
- lib/active_record/connection_adapters/cockroachdb/column_methods.rb
|
82
66
|
- lib/active_record/connection_adapters/cockroachdb/database_statements.rb
|
83
67
|
- lib/active_record/connection_adapters/cockroachdb/database_tasks.rb
|
84
|
-
- lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb
|
85
68
|
- lib/active_record/connection_adapters/cockroachdb/quoting.rb
|
86
69
|
- lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb
|
87
70
|
- lib/active_record/connection_adapters/cockroachdb/schema_statements.rb
|
88
|
-
- lib/active_record/connection_adapters/cockroachdb/setup.rb
|
89
|
-
- lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb
|
90
|
-
- lib/active_record/connection_adapters/cockroachdb/table_definition.rb
|
91
71
|
- lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb
|
92
72
|
- lib/active_record/connection_adapters/cockroachdb/type.rb
|
93
73
|
- lib/active_record/connection_adapters/cockroachdb_adapter.rb
|
@@ -97,7 +77,7 @@ licenses:
|
|
97
77
|
- Apache-2.0
|
98
78
|
metadata:
|
99
79
|
allowed_push_host: https://rubygems.org
|
100
|
-
post_install_message:
|
80
|
+
post_install_message:
|
101
81
|
rdoc_options: []
|
102
82
|
require_paths:
|
103
83
|
- lib
|
@@ -108,12 +88,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
88
|
version: '0'
|
109
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
90
|
requirements:
|
111
|
-
- - "
|
91
|
+
- - ">"
|
112
92
|
- !ruby/object:Gem::Version
|
113
|
-
version:
|
93
|
+
version: 1.3.1
|
114
94
|
requirements: []
|
115
|
-
rubygems_version: 3.
|
116
|
-
signing_key:
|
95
|
+
rubygems_version: 3.1.4
|
96
|
+
signing_key:
|
117
97
|
specification_version: 4
|
118
98
|
summary: CockroachDB adapter for ActiveRecord.
|
119
99
|
test_files: []
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RGeo
|
4
|
-
module ActiveRecord
|
5
|
-
##
|
6
|
-
# Extend rgeo-activerecord visitors to use PostGIS specific functionality
|
7
|
-
module SpatialToPostGISSql
|
8
|
-
def visit_in_spatial_context(node, collector)
|
9
|
-
# Use ST_GeomFromEWKT for EWKT geometries
|
10
|
-
if node.is_a?(String) && node =~ /SRID=[\d+]{0,};/
|
11
|
-
collector << "#{st_func('ST_GeomFromEWKT')}(#{quote(node)})"
|
12
|
-
else
|
13
|
-
super(node, collector)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
RGeo::ActiveRecord::SpatialToSql.prepend RGeo::ActiveRecord::SpatialToPostGISSql
|
20
|
-
|
21
|
-
module Arel # :nodoc:
|
22
|
-
module Visitors # :nodoc:
|
23
|
-
class CockroachDB < PostgreSQL # :nodoc:
|
24
|
-
include RGeo::ActiveRecord::SpatialToSql
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module ConnectionAdapters
|
5
|
-
module CockroachDB
|
6
|
-
module ColumnMethods
|
7
|
-
def spatial(name, options = {})
|
8
|
-
raise "You must set a type. For example: 't.spatial type: :st_point'" unless options[:type]
|
9
|
-
|
10
|
-
column(name, options[:type], **options)
|
11
|
-
end
|
12
|
-
|
13
|
-
def geography(name, options = {})
|
14
|
-
column(name, :geography, **options)
|
15
|
-
end
|
16
|
-
|
17
|
-
def geometry(name, options = {})
|
18
|
-
column(name, :geometry, **options)
|
19
|
-
end
|
20
|
-
|
21
|
-
def geometry_collection(name, options = {})
|
22
|
-
column(name, :geometry_collection, **options)
|
23
|
-
end
|
24
|
-
|
25
|
-
def line_string(name, options = {})
|
26
|
-
column(name, :line_string, **options)
|
27
|
-
end
|
28
|
-
|
29
|
-
def multi_line_string(name, options = {})
|
30
|
-
column(name, :multi_line_string, **options)
|
31
|
-
end
|
32
|
-
|
33
|
-
def multi_point(name, options = {})
|
34
|
-
column(name, :multi_point, **options)
|
35
|
-
end
|
36
|
-
|
37
|
-
def multi_polygon(name, options = {})
|
38
|
-
column(name, :multi_polygon, **options)
|
39
|
-
end
|
40
|
-
|
41
|
-
def st_point(name, options = {})
|
42
|
-
column(name, :st_point, **options)
|
43
|
-
end
|
44
|
-
|
45
|
-
def st_polygon(name, options = {})
|
46
|
-
column(name, :st_polygon, **options)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
PostgreSQL::Table.include CockroachDB::ColumnMethods
|
52
|
-
end
|
53
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
module ConnectionAdapters
|
5
|
-
module CockroachDB
|
6
|
-
module OID
|
7
|
-
class Spatial < Type::Value
|
8
|
-
# sql_type is a string that comes from the database definition
|
9
|
-
# examples:
|
10
|
-
# "geometry(Point,4326)"
|
11
|
-
# "geography(Point,4326)"
|
12
|
-
# "geometry(Polygon,4326) NOT NULL"
|
13
|
-
# "geometry(Geography,4326)"
|
14
|
-
def initialize(oid, sql_type)
|
15
|
-
@sql_type = sql_type
|
16
|
-
@geo_type, @srid, @has_z, @has_m = self.class.parse_sql_type(sql_type)
|
17
|
-
end
|
18
|
-
|
19
|
-
# sql_type: geometry, geometry(Point), geometry(Point,4326), ...
|
20
|
-
#
|
21
|
-
# returns [geo_type, srid, has_z, has_m]
|
22
|
-
# geo_type: geography, geometry, point, line_string, polygon, ...
|
23
|
-
# srid: 1234
|
24
|
-
# has_z: false
|
25
|
-
# has_m: false
|
26
|
-
def self.parse_sql_type(sql_type)
|
27
|
-
geo_type = nil
|
28
|
-
srid = 0
|
29
|
-
has_z = false
|
30
|
-
has_m = false
|
31
|
-
|
32
|
-
if sql_type =~ /(geography|geometry)\((.*)\)$/i
|
33
|
-
# geometry(Point)
|
34
|
-
# geometry(Point,4326)
|
35
|
-
params = Regexp.last_match(2).split(',')
|
36
|
-
if params.first =~ /([a-z]+[^zm])(z?)(m?)/i
|
37
|
-
has_z = Regexp.last_match(2).length > 0
|
38
|
-
has_m = Regexp.last_match(3).length > 0
|
39
|
-
geo_type = Regexp.last_match(1)
|
40
|
-
end
|
41
|
-
srid = Regexp.last_match(1).to_i if params.last =~ /(\d+)/
|
42
|
-
else
|
43
|
-
geo_type = sql_type
|
44
|
-
end
|
45
|
-
[geo_type, srid, has_z, has_m]
|
46
|
-
end
|
47
|
-
|
48
|
-
def spatial_factory
|
49
|
-
@spatial_factory ||=
|
50
|
-
RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
|
51
|
-
factory_attrs
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
|
-
def geographic?
|
56
|
-
@sql_type =~ /geography/
|
57
|
-
end
|
58
|
-
|
59
|
-
def spatial?
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
def type
|
64
|
-
geographic? ? :geography : :geometry
|
65
|
-
end
|
66
|
-
|
67
|
-
# support setting an RGeo object or a WKT string
|
68
|
-
def serialize(value)
|
69
|
-
return if value.nil?
|
70
|
-
|
71
|
-
geo_value = cast_value(value)
|
72
|
-
|
73
|
-
# TODO: - only valid types should be allowed
|
74
|
-
# e.g. linestring is not valid for point column
|
75
|
-
# raise "maybe should raise" unless RGeo::Feature::Geometry.check_type(geo_value)
|
76
|
-
|
77
|
-
RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true)
|
78
|
-
.generate(geo_value)
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def cast_value(value)
|
84
|
-
return if value.nil?
|
85
|
-
|
86
|
-
value.is_a?(String) ? parse_wkt(value) : value
|
87
|
-
end
|
88
|
-
|
89
|
-
# convert WKT string into RGeo object
|
90
|
-
def parse_wkt(string)
|
91
|
-
wkt_parser(string).parse(string)
|
92
|
-
rescue RGeo::Error::ParseError
|
93
|
-
nil
|
94
|
-
end
|
95
|
-
|
96
|
-
def binary_string?(string)
|
97
|
-
string[0] == "\x00" || string[0] == "\x01" || string[0, 4] =~ /[0-9a-fA-F]{4}/
|
98
|
-
end
|
99
|
-
|
100
|
-
def wkt_parser(string)
|
101
|
-
if binary_string?(string)
|
102
|
-
RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid)
|
103
|
-
else
|
104
|
-
RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def factory_attrs
|
109
|
-
{
|
110
|
-
geo_type: @geo_type.underscore,
|
111
|
-
has_m: @has_m,
|
112
|
-
has_z: @has_z,
|
113
|
-
srid: @srid,
|
114
|
-
sql_type: type.to_s
|
115
|
-
}
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecord # :nodoc:
|
4
|
-
module ConnectionAdapters # :nodoc:
|
5
|
-
module CockroachDB # :nodoc:
|
6
|
-
def self.initial_setup
|
7
|
-
::ActiveRecord::SchemaDumper.ignore_tables |= %w[
|
8
|
-
geography_columns
|
9
|
-
geometry_columns
|
10
|
-
layer
|
11
|
-
raster_columns
|
12
|
-
raster_overviews
|
13
|
-
spatial_ref_sys
|
14
|
-
topology
|
15
|
-
]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module ConnectionAdapters
|
3
|
-
module CockroachDB
|
4
|
-
class SpatialColumnInfo
|
5
|
-
def initialize(adapter, table_name)
|
6
|
-
@adapter = adapter
|
7
|
-
@table_name = table_name
|
8
|
-
end
|
9
|
-
|
10
|
-
def all
|
11
|
-
info = @adapter.query(
|
12
|
-
"SELECT f_geometry_column,coord_dimension,srid,type FROM geometry_columns WHERE f_table_name='#{@table_name}'"
|
13
|
-
)
|
14
|
-
result = {}
|
15
|
-
info.each do |row|
|
16
|
-
name = row[0]
|
17
|
-
type = row[3]
|
18
|
-
dimension = row[1].to_i
|
19
|
-
has_m = !!(type =~ /m$/i)
|
20
|
-
type.sub!(/m$/, '')
|
21
|
-
has_z = dimension > 3 || dimension == 3 && !has_m
|
22
|
-
result[name] = {
|
23
|
-
dimension: dimension,
|
24
|
-
has_m: has_m,
|
25
|
-
has_z: has_z,
|
26
|
-
name: name,
|
27
|
-
srid: row[2].to_i,
|
28
|
-
type: type
|
29
|
-
}
|
30
|
-
end
|
31
|
-
result
|
32
|
-
end
|
33
|
-
|
34
|
-
# do not query the database for non-spatial columns/tables
|
35
|
-
def get(column_name, type)
|
36
|
-
return unless CockroachDBAdapter.spatial_column_options(type.to_sym)
|
37
|
-
|
38
|
-
@spatial_column_info ||= all
|
39
|
-
@spatial_column_info[column_name]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecord # :nodoc:
|
4
|
-
module ConnectionAdapters # :nodoc:
|
5
|
-
module CockroachDB # :nodoc:
|
6
|
-
class TableDefinition < PostgreSQL::TableDefinition # :nodoc:
|
7
|
-
include ColumnMethods
|
8
|
-
|
9
|
-
# Support for spatial columns in tables
|
10
|
-
# super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
|
11
|
-
def new_column_definition(name, type, **options)
|
12
|
-
if (info = CockroachDBAdapter.spatial_column_options(type.to_sym))
|
13
|
-
if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
|
14
|
-
options.merge!(limit)
|
15
|
-
end
|
16
|
-
|
17
|
-
geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type || info[:type])
|
18
|
-
base_type = info[:type] || (options[:geographic] ? :geography : :geometry)
|
19
|
-
|
20
|
-
options[:limit] = ColumnDefinitionUtils.limit_from_options(geo_type, options)
|
21
|
-
options[:spatial_type] = geo_type
|
22
|
-
column = super(name, base_type, **options)
|
23
|
-
else
|
24
|
-
column = super(name, type, **options)
|
25
|
-
end
|
26
|
-
|
27
|
-
column
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
module ColumnDefinitionUtils
|
32
|
-
class << self
|
33
|
-
def geo_type(type = 'GEOMETRY')
|
34
|
-
g_type = type.to_s.delete('_').upcase
|
35
|
-
return 'POINT' if g_type == 'STPOINT'
|
36
|
-
return 'POLYGON' if g_type == 'STPOLYGON'
|
37
|
-
|
38
|
-
g_type
|
39
|
-
end
|
40
|
-
|
41
|
-
def limit_from_options(type, options = {})
|
42
|
-
spatial_type = geo_type(type)
|
43
|
-
spatial_type << 'Z' if options[:has_z]
|
44
|
-
spatial_type << 'M' if options[:has_m]
|
45
|
-
spatial_type << ",#{options[:srid] || default_srid(options)}"
|
46
|
-
spatial_type
|
47
|
-
end
|
48
|
-
|
49
|
-
def default_srid(options)
|
50
|
-
options[:geographic] ? 4326 : CockroachDBAdapter::DEFAULT_SRID
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|