pgslice 0.6.1 → 0.7.1
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/CHANGELOG.md +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +31 -23
- data/lib/pgslice/cli/add_partitions.rb +17 -17
- data/lib/pgslice/cli/analyze.rb +3 -1
- data/lib/pgslice/cli/fill.rb +5 -5
- data/lib/pgslice/cli/prep.rb +21 -24
- data/lib/pgslice/cli/unprep.rb +7 -2
- data/lib/pgslice/cli.rb +3 -5
- data/lib/pgslice/helpers.rb +2 -2
- data/lib/pgslice/table.rb +46 -22
- data/lib/pgslice/version.rb +1 -1
- metadata +4 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8084c5bbabb6b85b6e90f03d1895d95c4fa9130dcf68fbb0c0929b43132bf25
|
4
|
+
data.tar.gz: 6313d7ff50a64695fac17e31be5e3939f7982db3d0662629ba0841f4c5a71518
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82751357b5799a2ee7f3e36cff221a9d0a8a41c08bfc3cae1309e50a1af9d6fbd5392ee9ecd657051f03fc59440276d6d481266cf560da034a3012d2a79dee62
|
7
|
+
data.tar.gz: '06320641649d6a8caa68c2fdba355f0bc76755b13f37ae0e1afba53115f0a0e7df1c073e718308e591a7c91938283505730eaf638fb4959bacfdbbb8851bf956'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 0.7.1 (2025-07-27)
|
2
|
+
|
3
|
+
- Fixed `analyze` analyzing partitions twice with declarative partitioning
|
4
|
+
- Removed unnecessary query for `unprep` with declarative partitioning
|
5
|
+
|
6
|
+
## 0.7.0 (2025-05-26)
|
7
|
+
|
8
|
+
- Dropped support for Ruby < 3
|
9
|
+
- Dropped support for Postgres < 13
|
10
|
+
|
1
11
|
## 0.6.1 (2023-04-26)
|
2
12
|
|
3
13
|
- Fixed `uninitialized constant` error
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Postgres partitioning as easy as pie. Works great for both new and existing tabl
|
|
4
4
|
|
5
5
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
6
6
|
|
7
|
-
[](https://github.com/ankane/pgslice/actions)
|
8
8
|
|
9
9
|
## Install
|
10
10
|
|
@@ -14,7 +14,9 @@ pgslice is a command line tool. To install, run:
|
|
14
14
|
gem install pgslice
|
15
15
|
```
|
16
16
|
|
17
|
-
This will give you the `pgslice` command.
|
17
|
+
This will give you the `pgslice` command. If installation fails, you may need to install [dependencies](#dependencies).
|
18
|
+
|
19
|
+
You can also install it with [Homebrew](#homebrew) or [Docker](#docker).
|
18
20
|
|
19
21
|
## Steps
|
20
22
|
|
@@ -92,11 +94,11 @@ pgslice prep visits created_at month
|
|
92
94
|
```sql
|
93
95
|
BEGIN;
|
94
96
|
|
95
|
-
CREATE TABLE "public"."visits_intermediate" (LIKE "public"."visits" INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS) PARTITION BY RANGE ("created_at");
|
97
|
+
CREATE TABLE "public"."visits_intermediate" (LIKE "public"."visits" INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING STORAGE INCLUDING COMMENTS INCLUDING STATISTICS INCLUDING GENERATED INCLUDING COMPRESSION) PARTITION BY RANGE ("created_at");
|
96
98
|
|
97
99
|
CREATE INDEX ON "public"."visits_intermediate" USING btree ("created_at");
|
98
100
|
|
99
|
-
COMMENT ON TABLE "public"."visits_intermediate" is 'column:
|
101
|
+
COMMENT ON TABLE "public"."visits_intermediate" is 'column:created_at,period:month,cast:date,version:3';
|
100
102
|
|
101
103
|
COMMIT;
|
102
104
|
```
|
@@ -108,17 +110,17 @@ pgslice add_partitions visits --intermediate --past 1 --future 1
|
|
108
110
|
```sql
|
109
111
|
BEGIN;
|
110
112
|
|
111
|
-
CREATE TABLE "public"."
|
113
|
+
CREATE TABLE "public"."visits_202408" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-08-01') TO ('2024-09-01');
|
112
114
|
|
113
|
-
ALTER TABLE "public"."
|
115
|
+
ALTER TABLE "public"."visits_202408" ADD PRIMARY KEY ("id");
|
114
116
|
|
115
|
-
CREATE TABLE "public"."
|
117
|
+
CREATE TABLE "public"."visits_202409" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-09-01') TO ('2024-10-01');
|
116
118
|
|
117
|
-
ALTER TABLE "public"."
|
119
|
+
ALTER TABLE "public"."visits_202409" ADD PRIMARY KEY ("id");
|
118
120
|
|
119
|
-
CREATE TABLE "public"."
|
121
|
+
CREATE TABLE "public"."visits_202410" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-10-01') TO ('2024-11-01');
|
120
122
|
|
121
|
-
ALTER TABLE "public"."
|
123
|
+
ALTER TABLE "public"."visits_202410" ADD PRIMARY KEY ("id");
|
122
124
|
|
123
125
|
COMMIT;
|
124
126
|
```
|
@@ -131,17 +133,17 @@ pgslice fill visits
|
|
131
133
|
/* 1 of 3 */
|
132
134
|
INSERT INTO "public"."visits_intermediate" ("id", "user_id", "ip", "created_at")
|
133
135
|
SELECT "id", "user_id", "ip", "created_at" FROM "public"."visits"
|
134
|
-
WHERE "id" > 0 AND "id" <= 10000 AND "created_at" >= '
|
136
|
+
WHERE "id" > 0 AND "id" <= 10000 AND "created_at" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
|
135
137
|
|
136
138
|
/* 2 of 3 */
|
137
139
|
INSERT INTO "public"."visits_intermediate" ("id", "user_id", "ip", "created_at")
|
138
140
|
SELECT "id", "user_id", "ip", "created_at" FROM "public"."visits"
|
139
|
-
WHERE "id" > 10000 AND "id" <= 20000 AND "created_at" >= '
|
141
|
+
WHERE "id" > 10000 AND "id" <= 20000 AND "created_at" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
|
140
142
|
|
141
143
|
/* 3 of 3 */
|
142
144
|
INSERT INTO "public"."visits_intermediate" ("id", "user_id", "ip", "created_at")
|
143
145
|
SELECT "id", "user_id", "ip", "created_at" FROM "public"."visits"
|
144
|
-
WHERE "id" > 20000 AND "id" <= 30000 AND "created_at" >= '
|
146
|
+
WHERE "id" > 20000 AND "id" <= 30000 AND "created_at" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
|
145
147
|
```
|
146
148
|
|
147
149
|
```sh
|
@@ -149,11 +151,11 @@ pgslice analyze visits
|
|
149
151
|
```
|
150
152
|
|
151
153
|
```sql
|
152
|
-
ANALYZE VERBOSE "public"."
|
154
|
+
ANALYZE VERBOSE "public"."visits_202408";
|
153
155
|
|
154
|
-
ANALYZE VERBOSE "public"."
|
156
|
+
ANALYZE VERBOSE "public"."visits_202409";
|
155
157
|
|
156
|
-
ANALYZE VERBOSE "public"."
|
158
|
+
ANALYZE VERBOSE "public"."visits_202410";
|
157
159
|
|
158
160
|
ANALYZE VERBOSE "public"."visits_intermediate";
|
159
161
|
```
|
@@ -217,14 +219,14 @@ WHERE
|
|
217
219
|
Back up and drop older partitions each day, month, or year.
|
218
220
|
|
219
221
|
```sh
|
220
|
-
pg_dump -c -Fc -t <table>
|
221
|
-
psql -c "DROP TABLE <table>
|
222
|
+
pg_dump -c -Fc -t <table>_202409 $PGSLICE_URL > <table>_202409.dump
|
223
|
+
psql -c "DROP TABLE <table>_202409" $PGSLICE_URL
|
222
224
|
```
|
223
225
|
|
224
226
|
If you use [Amazon S3](https://aws.amazon.com/s3/) for backups, [s3cmd](https://github.com/s3tools/s3cmd) is a nice tool.
|
225
227
|
|
226
228
|
```sh
|
227
|
-
s3cmd put <table>
|
229
|
+
s3cmd put <table>_202409.dump s3://<s3-bucket>/<table>_202409.dump
|
228
230
|
```
|
229
231
|
|
230
232
|
## Schema Updates
|
@@ -267,7 +269,7 @@ SELECT * FROM
|
|
267
269
|
WHERE
|
268
270
|
user_id = 123 AND
|
269
271
|
-- for performance
|
270
|
-
created_at >= '
|
272
|
+
created_at >= '2024-09-01' AND created_at < '2024-09-02'
|
271
273
|
```
|
272
274
|
|
273
275
|
For this to be effective, ensure `constraint_exclusion` is set to `partition` (the default value) or `on`.
|
@@ -299,12 +301,14 @@ You can also use pgslice to reduce the size of a table without partitioning by c
|
|
299
301
|
```sh
|
300
302
|
pgslice prep <table> --no-partition
|
301
303
|
pgslice fill <table> --where "id > 1000" # use any conditions
|
304
|
+
pgslice analyze <table>
|
302
305
|
pgslice swap <table>
|
306
|
+
pgslice fill <table> --where "id > 1000" --swapped
|
303
307
|
```
|
304
308
|
|
305
309
|
## Triggers
|
306
310
|
|
307
|
-
Triggers aren’t copied from the original table. You can set up triggers on the intermediate table if needed.
|
311
|
+
Triggers aren’t copied from the original table. You can set up triggers on the intermediate table if needed.
|
308
312
|
|
309
313
|
## Data Protection
|
310
314
|
|
@@ -317,7 +321,7 @@ Always make sure your [connection is secure](https://ankane.org/postgres-sslmode
|
|
317
321
|
With Homebrew, you can use:
|
318
322
|
|
319
323
|
```sh
|
320
|
-
brew install
|
324
|
+
brew install pgslice
|
321
325
|
```
|
322
326
|
|
323
327
|
### Docker
|
@@ -374,6 +378,10 @@ Also check out:
|
|
374
378
|
- [PgHero](https://github.com/ankane/pghero) - A performance dashboard for Postgres
|
375
379
|
- [pgsync](https://github.com/ankane/pgsync) - Sync Postgres data to your local machine
|
376
380
|
|
381
|
+
## History
|
382
|
+
|
383
|
+
View the [changelog](https://github.com/ankane/pgslice/blob/master/CHANGELOG.md)
|
384
|
+
|
377
385
|
## Contributing
|
378
386
|
|
379
387
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
@@ -396,7 +404,7 @@ bundle exec rake test
|
|
396
404
|
To test against different versions of Postgres with Docker, use:
|
397
405
|
|
398
406
|
```sh
|
399
|
-
docker run -p=8000:5432 postgres:
|
407
|
+
docker run -p=8000:5432 postgres:16
|
400
408
|
TZ=Etc/UTC PGSLICE_URL=postgres://postgres@localhost:8000/postgres bundle exec rake
|
401
409
|
```
|
402
410
|
|
@@ -63,14 +63,14 @@ module PgSlice
|
|
63
63
|
added_partitions << partition
|
64
64
|
|
65
65
|
if declarative
|
66
|
-
queries <<
|
67
|
-
CREATE TABLE #{quote_table(partition)} PARTITION OF #{quote_table(table)} FOR VALUES FROM (#{sql_date(day, cast, false)}) TO (#{sql_date(advance_date(day, period, 1), cast, false)})#{tablespace_str};
|
66
|
+
queries << <<~SQL
|
67
|
+
CREATE TABLE #{quote_table(partition)} PARTITION OF #{quote_table(table)} FOR VALUES FROM (#{sql_date(day, cast, false)}) TO (#{sql_date(advance_date(day, period, 1), cast, false)})#{tablespace_str};
|
68
68
|
SQL
|
69
69
|
else
|
70
|
-
queries <<
|
71
|
-
CREATE TABLE #{quote_table(partition)}
|
72
|
-
|
73
|
-
|
70
|
+
queries << <<~SQL
|
71
|
+
CREATE TABLE #{quote_table(partition)}
|
72
|
+
(CHECK (#{quote_ident(field)} >= #{sql_date(day, cast)} AND #{quote_ident(field)} < #{sql_date(advance_date(day, period, 1), cast)}))
|
73
|
+
INHERITS (#{quote_table(table)})#{tablespace_str};
|
74
74
|
SQL
|
75
75
|
end
|
76
76
|
|
@@ -115,17 +115,17 @@ CREATE TABLE #{quote_table(partition)}
|
|
115
115
|
trigger_defs = current_defs + future_defs + past_defs.reverse
|
116
116
|
|
117
117
|
if trigger_defs.any?
|
118
|
-
queries <<
|
119
|
-
CREATE OR REPLACE FUNCTION #{quote_ident(trigger_name)}()
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
118
|
+
queries << <<~SQL
|
119
|
+
CREATE OR REPLACE FUNCTION #{quote_ident(trigger_name)}()
|
120
|
+
RETURNS trigger AS $$
|
121
|
+
BEGIN
|
122
|
+
IF #{trigger_defs.join("\n ELSIF ")}
|
123
|
+
ELSE
|
124
|
+
RAISE EXCEPTION 'Date out of range. Ensure partitions are created.';
|
125
|
+
END IF;
|
126
|
+
RETURN NULL;
|
127
|
+
END;
|
128
|
+
$$ LANGUAGE plpgsql;
|
129
129
|
SQL
|
130
130
|
end
|
131
131
|
end
|
data/lib/pgslice/cli/analyze.rb
CHANGED
@@ -5,8 +5,10 @@ module PgSlice
|
|
5
5
|
def analyze(table)
|
6
6
|
table = create_table(table)
|
7
7
|
parent_table = options[:swapped] ? table : table.intermediate_table
|
8
|
+
assert_table(parent_table)
|
8
9
|
|
9
|
-
|
10
|
+
_, _, _, _, declarative, _ = parent_table.fetch_settings(table.trigger_name)
|
11
|
+
analyze_list = declarative ? [parent_table] : (parent_table.partitions + [parent_table])
|
10
12
|
run_queries_without_transaction(analyze_list.map { |t| "ANALYZE VERBOSE #{quote_table(t)};" })
|
11
13
|
end
|
12
14
|
end
|
data/lib/pgslice/cli/fill.rb
CHANGED
@@ -82,11 +82,11 @@ module PgSlice
|
|
82
82
|
where << " AND #{options[:where]}"
|
83
83
|
end
|
84
84
|
|
85
|
-
query =
|
86
|
-
/* #{i} of #{batch_count} */
|
87
|
-
INSERT INTO #{quote_table(dest_table)} (#{fields})
|
88
|
-
|
89
|
-
|
85
|
+
query = <<~SQL
|
86
|
+
/* #{i} of #{batch_count} */
|
87
|
+
INSERT INTO #{quote_table(dest_table)} (#{fields})
|
88
|
+
SELECT #{fields} FROM #{quote_table(source_table)}
|
89
|
+
WHERE #{where}
|
90
90
|
SQL
|
91
91
|
|
92
92
|
run_query(query)
|
data/lib/pgslice/cli/prep.rb
CHANGED
@@ -4,7 +4,7 @@ module PgSlice
|
|
4
4
|
option :partition, type: :boolean, default: true, desc: "Partition the table"
|
5
5
|
option :trigger_based, type: :boolean, default: false, desc: "Use trigger-based partitioning"
|
6
6
|
option :test_version, type: :numeric, hide: true
|
7
|
-
def prep(table, column=nil, period=nil)
|
7
|
+
def prep(table, column = nil, period = nil)
|
8
8
|
table = create_table(table)
|
9
9
|
intermediate_table = table.intermediate_table
|
10
10
|
trigger_name = table.trigger_name
|
@@ -33,15 +33,12 @@ module PgSlice
|
|
33
33
|
declarative = version > 1
|
34
34
|
|
35
35
|
if declarative && options[:partition]
|
36
|
-
including = ["DEFAULTS", "CONSTRAINTS", "STORAGE", "COMMENTS", "STATISTICS"]
|
37
|
-
if server_version_num >= 120000
|
38
|
-
including << "GENERATED"
|
39
|
-
end
|
36
|
+
including = ["DEFAULTS", "CONSTRAINTS", "STORAGE", "COMMENTS", "STATISTICS", "GENERATED"]
|
40
37
|
if server_version_num >= 140000
|
41
38
|
including << "COMPRESSION"
|
42
39
|
end
|
43
|
-
queries <<
|
44
|
-
CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} #{including.map { |v| "INCLUDING #{v}" }.join(" ")}) PARTITION BY RANGE (#{quote_ident(column)});
|
40
|
+
queries << <<~SQL
|
41
|
+
CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} #{including.map { |v| "INCLUDING #{v}" }.join(" ")}) PARTITION BY RANGE (#{quote_ident(column)});
|
45
42
|
SQL
|
46
43
|
|
47
44
|
if version == 3
|
@@ -57,12 +54,12 @@ CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} #{in
|
|
57
54
|
|
58
55
|
# add comment
|
59
56
|
cast = table.column_cast(column)
|
60
|
-
queries <<
|
61
|
-
COMMENT ON TABLE #{quote_table(intermediate_table)} IS 'column:#{column},period:#{period},cast:#{cast},version:#{version}';
|
57
|
+
queries << <<~SQL
|
58
|
+
COMMENT ON TABLE #{quote_table(intermediate_table)} IS 'column:#{column},period:#{period},cast:#{cast},version:#{version}';
|
62
59
|
SQL
|
63
60
|
else
|
64
|
-
queries <<
|
65
|
-
CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} INCLUDING ALL);
|
61
|
+
queries << <<~SQL
|
62
|
+
CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} INCLUDING ALL);
|
66
63
|
SQL
|
67
64
|
|
68
65
|
table.foreign_keys.each do |fk_def|
|
@@ -71,24 +68,24 @@ CREATE TABLE #{quote_table(intermediate_table)} (LIKE #{quote_table(table)} INCL
|
|
71
68
|
end
|
72
69
|
|
73
70
|
if options[:partition] && !declarative
|
74
|
-
queries <<
|
75
|
-
CREATE FUNCTION #{quote_ident(trigger_name)}()
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
queries << <<~SQL
|
72
|
+
CREATE FUNCTION #{quote_ident(trigger_name)}()
|
73
|
+
RETURNS trigger AS $$
|
74
|
+
BEGIN
|
75
|
+
RAISE EXCEPTION 'Create partitions first.';
|
76
|
+
END;
|
77
|
+
$$ LANGUAGE plpgsql;
|
81
78
|
SQL
|
82
79
|
|
83
|
-
queries <<
|
84
|
-
CREATE TRIGGER #{quote_ident(trigger_name)}
|
85
|
-
|
86
|
-
|
80
|
+
queries << <<~SQL
|
81
|
+
CREATE TRIGGER #{quote_ident(trigger_name)}
|
82
|
+
BEFORE INSERT ON #{quote_table(intermediate_table)}
|
83
|
+
FOR EACH ROW EXECUTE PROCEDURE #{quote_ident(trigger_name)}();
|
87
84
|
SQL
|
88
85
|
|
89
86
|
cast = table.column_cast(column)
|
90
|
-
queries <<
|
91
|
-
COMMENT ON TRIGGER #{quote_ident(trigger_name)} ON #{quote_table(intermediate_table)} IS 'column:#{column},period:#{period},cast:#{cast}';
|
87
|
+
queries << <<~SQL
|
88
|
+
COMMENT ON TRIGGER #{quote_ident(trigger_name)} ON #{quote_table(intermediate_table)} IS 'column:#{column},period:#{period},cast:#{cast}';
|
92
89
|
SQL
|
93
90
|
end
|
94
91
|
|
data/lib/pgslice/cli/unprep.rb
CHANGED
@@ -9,9 +9,14 @@ module PgSlice
|
|
9
9
|
assert_table(intermediate_table)
|
10
10
|
|
11
11
|
queries = [
|
12
|
-
"DROP TABLE #{quote_table(intermediate_table)} CASCADE;"
|
13
|
-
"DROP FUNCTION IF EXISTS #{quote_ident(trigger_name)}();"
|
12
|
+
"DROP TABLE #{quote_table(intermediate_table)} CASCADE;"
|
14
13
|
]
|
14
|
+
|
15
|
+
_, _, _, _, declarative, _ = intermediate_table.fetch_settings(table.trigger_name)
|
16
|
+
unless declarative
|
17
|
+
queries << "DROP FUNCTION IF EXISTS #{quote_ident(trigger_name)}();"
|
18
|
+
end
|
19
|
+
|
15
20
|
run_queries(queries)
|
16
21
|
end
|
17
22
|
end
|
data/lib/pgslice/cli.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module PgSlice
|
2
2
|
class CLI < Thor
|
3
3
|
class << self
|
4
|
-
attr_accessor :instance
|
4
|
+
attr_accessor :instance, :exit_on_failure
|
5
|
+
alias_method :exit_on_failure?, :exit_on_failure
|
5
6
|
end
|
7
|
+
self.exit_on_failure = true
|
6
8
|
|
7
9
|
include Helpers
|
8
10
|
|
@@ -13,10 +15,6 @@ module PgSlice
|
|
13
15
|
|
14
16
|
map %w[--version -v] => :version
|
15
17
|
|
16
|
-
def self.exit_on_failure?
|
17
|
-
ENV["PGSLICE_ENV"] != "test"
|
18
|
-
end
|
19
|
-
|
20
18
|
def initialize(*args)
|
21
19
|
PgSlice::CLI.instance = self
|
22
20
|
$stdout.sync = true
|
data/lib/pgslice/helpers.rb
CHANGED
@@ -41,8 +41,8 @@ module PgSlice
|
|
41
41
|
say message
|
42
42
|
end
|
43
43
|
@server_version_num = conn.exec("SHOW server_version_num")[0]["server_version_num"].to_i
|
44
|
-
if @server_version_num <
|
45
|
-
abort "This version of pgslice requires Postgres
|
44
|
+
if @server_version_num < 130000
|
45
|
+
abort "This version of pgslice requires Postgres 13+"
|
46
46
|
end
|
47
47
|
conn
|
48
48
|
end
|
data/lib/pgslice/table.rb
CHANGED
@@ -12,16 +12,24 @@ module PgSlice
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def exists?
|
15
|
-
|
15
|
+
query = <<~SQL
|
16
|
+
SELECT COUNT(*) FROM pg_catalog.pg_tables
|
17
|
+
WHERE schemaname = $1 AND tablename = $2
|
18
|
+
SQL
|
19
|
+
execute(query, [schema, name]).first["count"].to_i > 0
|
16
20
|
end
|
17
21
|
|
18
22
|
def columns
|
19
|
-
|
23
|
+
query = <<~SQL
|
24
|
+
SELECT column_name FROM information_schema.columns
|
25
|
+
WHERE table_schema = $1 AND table_name = $2 AND is_generated = 'NEVER'
|
26
|
+
SQL
|
27
|
+
execute(query, [schema, name]).map { |r| r["column_name"] }
|
20
28
|
end
|
21
29
|
|
22
30
|
# http://www.dbforums.com/showthread.php?1667561-How-to-list-sequences-and-the-columns-by-SQL
|
23
31
|
def sequences
|
24
|
-
query =
|
32
|
+
query = <<~SQL
|
25
33
|
SELECT
|
26
34
|
a.attname AS related_column,
|
27
35
|
n.nspname AS sequence_schema,
|
@@ -41,13 +49,17 @@ module PgSlice
|
|
41
49
|
end
|
42
50
|
|
43
51
|
def foreign_keys
|
44
|
-
|
52
|
+
query = <<~SQL
|
53
|
+
SELECT pg_get_constraintdef(oid) FROM pg_constraint
|
54
|
+
WHERE conrelid = $1::regclass AND contype ='f'
|
55
|
+
SQL
|
56
|
+
execute(query, [quote_table]).map { |r| r["pg_get_constraintdef"] }
|
45
57
|
end
|
46
58
|
|
47
59
|
# https://stackoverflow.com/a/20537829
|
48
60
|
# TODO can simplify with array_position in Postgres 9.5+
|
49
61
|
def primary_key
|
50
|
-
query =
|
62
|
+
query = <<~SQL
|
51
63
|
SELECT
|
52
64
|
pg_attribute.attname,
|
53
65
|
format_type(pg_attribute.atttypid, pg_attribute.atttypmod),
|
@@ -69,7 +81,11 @@ module PgSlice
|
|
69
81
|
end
|
70
82
|
|
71
83
|
def index_defs
|
72
|
-
|
84
|
+
query = <<~SQL
|
85
|
+
SELECT pg_get_indexdef(indexrelid) FROM pg_index
|
86
|
+
WHERE indrelid = $1::regclass AND indisprimary = 'f'
|
87
|
+
SQL
|
88
|
+
execute(query, [quote_table]).map { |r| r["pg_get_indexdef"] }
|
73
89
|
end
|
74
90
|
|
75
91
|
def quote_table
|
@@ -89,17 +105,25 @@ module PgSlice
|
|
89
105
|
end
|
90
106
|
|
91
107
|
def column_cast(column)
|
92
|
-
|
108
|
+
query = <<~SQL
|
109
|
+
SELECT data_type FROM information_schema.columns
|
110
|
+
WHERE table_schema = $1 AND table_name = $2 AND column_name = $3
|
111
|
+
SQL
|
112
|
+
data_type = execute(query, [schema, name, column])[0]["data_type"]
|
93
113
|
data_type == "timestamp with time zone" ? "timestamptz" : "date"
|
94
114
|
end
|
95
115
|
|
96
116
|
def max_id(primary_key, below: nil, where: nil)
|
97
117
|
query = "SELECT MAX(#{quote_ident(primary_key)}) FROM #{quote_table}"
|
98
118
|
conditions = []
|
99
|
-
|
119
|
+
params = []
|
120
|
+
if below
|
121
|
+
conditions << "#{quote_ident(primary_key)} <= $1"
|
122
|
+
params << below
|
123
|
+
end
|
100
124
|
conditions << where if where
|
101
125
|
query << " WHERE #{conditions.join(" AND ")}" if conditions.any?
|
102
|
-
execute(query)[0]["max"].to_i
|
126
|
+
execute(query, params)[0]["max"].to_i
|
103
127
|
end
|
104
128
|
|
105
129
|
def min_id(primary_key, column, cast, starting_time, where)
|
@@ -113,15 +137,15 @@ module PgSlice
|
|
113
137
|
|
114
138
|
# ensure this returns partitions in the correct order
|
115
139
|
def partitions
|
116
|
-
query =
|
140
|
+
query = <<~SQL
|
117
141
|
SELECT
|
118
|
-
nmsp_child.nspname
|
119
|
-
child.relname
|
142
|
+
nmsp_child.nspname AS schema,
|
143
|
+
child.relname AS name
|
120
144
|
FROM pg_inherits
|
121
|
-
JOIN pg_class parent
|
122
|
-
JOIN pg_class child
|
123
|
-
JOIN pg_namespace nmsp_parent
|
124
|
-
JOIN pg_namespace nmsp_child
|
145
|
+
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
146
|
+
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
147
|
+
JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace
|
148
|
+
JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace
|
125
149
|
WHERE
|
126
150
|
nmsp_parent.nspname = $1 AND
|
127
151
|
parent.relname = $2
|
@@ -131,11 +155,15 @@ module PgSlice
|
|
131
155
|
end
|
132
156
|
|
133
157
|
def fetch_comment
|
134
|
-
execute("SELECT obj_description(
|
158
|
+
execute("SELECT obj_description($1::regclass) AS comment", [quote_table])[0]
|
135
159
|
end
|
136
160
|
|
137
161
|
def fetch_trigger(trigger_name)
|
138
|
-
|
162
|
+
query = <<~SQL
|
163
|
+
SELECT obj_description(oid, 'pg_trigger') AS comment FROM pg_trigger
|
164
|
+
WHERE tgname = $1 AND tgrelid = $2::regclass
|
165
|
+
SQL
|
166
|
+
execute(query, [trigger_name, quote_table])[0]
|
139
167
|
end
|
140
168
|
|
141
169
|
# legacy
|
@@ -186,10 +214,6 @@ module PgSlice
|
|
186
214
|
PG::Connection.quote_ident(value)
|
187
215
|
end
|
188
216
|
|
189
|
-
def regclass
|
190
|
-
"#{escape_literal(quote_table)}::regclass"
|
191
|
-
end
|
192
|
-
|
193
217
|
def sql_date(time, cast, add_cast = true)
|
194
218
|
if cast == "timestamptz"
|
195
219
|
fmt = "%Y-%m-%d %H:%M:%S UTC"
|
data/lib/pgslice/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgslice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: pg
|
@@ -38,7 +37,6 @@ dependencies:
|
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '0'
|
41
|
-
description:
|
42
40
|
email: andrew@ankane.org
|
43
41
|
executables:
|
44
42
|
- pgslice
|
@@ -65,7 +63,6 @@ homepage: https://github.com/ankane/pgslice
|
|
65
63
|
licenses:
|
66
64
|
- MIT
|
67
65
|
metadata: {}
|
68
|
-
post_install_message:
|
69
66
|
rdoc_options: []
|
70
67
|
require_paths:
|
71
68
|
- lib
|
@@ -73,15 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
70
|
requirements:
|
74
71
|
- - ">="
|
75
72
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
73
|
+
version: '3'
|
77
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
75
|
requirements:
|
79
76
|
- - ">="
|
80
77
|
- !ruby/object:Gem::Version
|
81
78
|
version: '0'
|
82
79
|
requirements: []
|
83
|
-
rubygems_version: 3.
|
84
|
-
signing_key:
|
80
|
+
rubygems_version: 3.6.9
|
85
81
|
specification_version: 4
|
86
82
|
summary: Postgres partitioning as easy as pie
|
87
83
|
test_files: []
|