pgslice 0.7.0 → 0.7.2
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 +9 -0
- data/README.md +23 -19
- data/lib/pgslice/cli/add_partitions.rb +22 -18
- data/lib/pgslice/cli/analyze.rb +3 -1
- data/lib/pgslice/cli/fill.rb +6 -6
- data/lib/pgslice/cli/prep.rb +20 -23
- data/lib/pgslice/cli/swap.rb +1 -1
- data/lib/pgslice/cli/unprep.rb +7 -2
- data/lib/pgslice/cli.rb +3 -5
- data/lib/pgslice/helpers.rb +18 -5
- data/lib/pgslice/table.rb +59 -34
- data/lib/pgslice/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 80b19b5f7b8bc83c0e75061952d6b26b1dc6ddba6eec34c1ca6a25ec53050201
|
|
4
|
+
data.tar.gz: b59c9d219b0bc539b5e45c078ff7bdd9bf3df9a3856574f811a19a33052c58bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db38bae9e3a120383329c1a9dbb2dc031700774bc57b0e5153a49c5f1683018d3752d363ea35976ca09f3da36ffa249f2bd9cdef74797461af60302089facef9
|
|
7
|
+
data.tar.gz: f4d51f9a864901a7305224b720c3e261a95a35584dc42d2559ca15068d5c2bea87755c4321c57aa76d60413c742b3cf5b767b731e759c83b2ce8c69502ecd4a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 0.7.2 (2026-01-05)
|
|
2
|
+
|
|
3
|
+
- Added support for Ruby 4.0
|
|
4
|
+
|
|
5
|
+
## 0.7.1 (2025-07-27)
|
|
6
|
+
|
|
7
|
+
- Fixed `analyze` analyzing partitions twice with declarative partitioning
|
|
8
|
+
- Removed unnecessary query for `unprep` with declarative partitioning
|
|
9
|
+
|
|
1
10
|
## 0.7.0 (2025-05-26)
|
|
2
11
|
|
|
3
12
|
- Dropped support for Ruby < 3
|
data/README.md
CHANGED
|
@@ -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
|
|
|
@@ -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_202508" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2025-08-01') TO ('2025-09-01');
|
|
112
114
|
|
|
113
|
-
ALTER TABLE "public"."
|
|
115
|
+
ALTER TABLE "public"."visits_202508" ADD PRIMARY KEY ("id");
|
|
114
116
|
|
|
115
|
-
CREATE TABLE "public"."
|
|
117
|
+
CREATE TABLE "public"."visits_202509" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2025-09-01') TO ('2025-10-01');
|
|
116
118
|
|
|
117
|
-
ALTER TABLE "public"."
|
|
119
|
+
ALTER TABLE "public"."visits_202509" ADD PRIMARY KEY ("id");
|
|
118
120
|
|
|
119
|
-
CREATE TABLE "public"."
|
|
121
|
+
CREATE TABLE "public"."visits_202510" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2025-10-01') TO ('2025-11-01');
|
|
120
122
|
|
|
121
|
-
ALTER TABLE "public"."
|
|
123
|
+
ALTER TABLE "public"."visits_202510" 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" >= '2025-08-01'::date AND "created_at" < '2025-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" >= '2025-08-01'::date AND "created_at" < '2025-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" >= '2025-08-01'::date AND "created_at" < '2025-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_202508";
|
|
153
155
|
|
|
154
|
-
ANALYZE VERBOSE "public"."
|
|
156
|
+
ANALYZE VERBOSE "public"."visits_202509";
|
|
155
157
|
|
|
156
|
-
ANALYZE VERBOSE "public"."
|
|
158
|
+
ANALYZE VERBOSE "public"."visits_202510";
|
|
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>_202509 $PGSLICE_URL > <table>_202509.dump
|
|
223
|
+
psql -c "DROP TABLE <table>_202509" $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>_202509.dump s3://<s3-bucket>/<table>_202509.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 >= '2025-09-01' AND created_at < '2025-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
|
|
@@ -13,7 +13,11 @@ module PgSlice
|
|
|
13
13
|
assert_table(table)
|
|
14
14
|
|
|
15
15
|
future = options[:future]
|
|
16
|
+
# TODO uncomment in 0.8.0
|
|
17
|
+
# abort "--future cannot be negative" if future < 0
|
|
16
18
|
past = options[:past]
|
|
19
|
+
# TODO uncomment in 0.8.0
|
|
20
|
+
# abort "--past cannot be negative" if past < 0
|
|
17
21
|
tablespace = options[:tablespace]
|
|
18
22
|
range = (-1 * past)..future
|
|
19
23
|
|
|
@@ -27,7 +31,7 @@ module PgSlice
|
|
|
27
31
|
queries = []
|
|
28
32
|
|
|
29
33
|
if needs_comment
|
|
30
|
-
queries << "COMMENT ON TRIGGER #{quote_ident(trigger_name)} ON #{quote_table(table)} IS
|
|
34
|
+
queries << "COMMENT ON TRIGGER #{quote_ident(trigger_name)} ON #{quote_table(table)} IS #{quote("column:#{field},period:#{period},cast:#{cast}")};"
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
# today = utc date
|
|
@@ -63,14 +67,14 @@ module PgSlice
|
|
|
63
67
|
added_partitions << partition
|
|
64
68
|
|
|
65
69
|
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};
|
|
70
|
+
queries << <<~SQL
|
|
71
|
+
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
72
|
SQL
|
|
69
73
|
else
|
|
70
|
-
queries <<
|
|
71
|
-
CREATE TABLE #{quote_table(partition)}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
queries << <<~SQL
|
|
75
|
+
CREATE TABLE #{quote_table(partition)}
|
|
76
|
+
(CHECK (#{quote_ident(field)} >= #{sql_date(day, cast)} AND #{quote_ident(field)} < #{sql_date(advance_date(day, period, 1), cast)}))
|
|
77
|
+
INHERITS (#{quote_table(table)})#{tablespace_str};
|
|
74
78
|
SQL
|
|
75
79
|
end
|
|
76
80
|
|
|
@@ -115,17 +119,17 @@ CREATE TABLE #{quote_table(partition)}
|
|
|
115
119
|
trigger_defs = current_defs + future_defs + past_defs.reverse
|
|
116
120
|
|
|
117
121
|
if trigger_defs.any?
|
|
118
|
-
queries <<
|
|
119
|
-
CREATE OR REPLACE FUNCTION #{quote_ident(trigger_name)}()
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
queries << <<~SQL
|
|
123
|
+
CREATE OR REPLACE FUNCTION #{quote_ident(trigger_name)}()
|
|
124
|
+
RETURNS trigger AS $$
|
|
125
|
+
BEGIN
|
|
126
|
+
IF #{trigger_defs.join("\n ELSIF ")}
|
|
127
|
+
ELSE
|
|
128
|
+
RAISE EXCEPTION 'Date out of range. Ensure partitions are created.';
|
|
129
|
+
END IF;
|
|
130
|
+
RETURN NULL;
|
|
131
|
+
END;
|
|
132
|
+
$$ LANGUAGE plpgsql;
|
|
129
133
|
SQL
|
|
130
134
|
end
|
|
131
135
|
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
|
@@ -74,7 +74,7 @@ module PgSlice
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
while starting_id < max_source_id
|
|
77
|
-
where = "#{quote_ident(primary_key)} > #{starting_id} AND #{quote_ident(primary_key)} <= #{starting_id + batch_size}"
|
|
77
|
+
where = "#{quote_ident(primary_key)} > #{quote(starting_id)} AND #{quote_ident(primary_key)} <= #{quote(starting_id + batch_size)}"
|
|
78
78
|
if starting_time
|
|
79
79
|
where << " AND #{quote_ident(field)} >= #{sql_date(starting_time, cast)} AND #{quote_ident(field)} < #{sql_date(ending_time, cast)}"
|
|
80
80
|
end
|
|
@@ -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
|
@@ -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
|
|
57
|
+
queries << <<~SQL
|
|
58
|
+
COMMENT ON TABLE #{quote_table(intermediate_table)} IS #{quote("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
|
|
87
|
+
queries << <<~SQL
|
|
88
|
+
COMMENT ON TRIGGER #{quote_ident(trigger_name)} ON #{quote_table(intermediate_table)} IS #{quote("column:#{column},period:#{period},cast:#{cast}")};
|
|
92
89
|
SQL
|
|
93
90
|
end
|
|
94
91
|
|
data/lib/pgslice/cli/swap.rb
CHANGED
|
@@ -20,7 +20,7 @@ module PgSlice
|
|
|
20
20
|
queries << "ALTER SEQUENCE #{quote_ident(sequence["sequence_schema"])}.#{quote_ident(sequence["sequence_name"])} OWNED BY #{quote_table(table)}.#{quote_ident(sequence["related_column"])};"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
queries.unshift("SET LOCAL lock_timeout = #{
|
|
23
|
+
queries.unshift("SET LOCAL lock_timeout = #{quote(options[:lock_timeout])};")
|
|
24
24
|
|
|
25
25
|
run_queries(queries)
|
|
26
26
|
end
|
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
|
@@ -77,7 +77,7 @@ module PgSlice
|
|
|
77
77
|
begin
|
|
78
78
|
execute(query)
|
|
79
79
|
rescue PG::ServerError => e
|
|
80
|
-
abort
|
|
80
|
+
abort "#{e.class.name}: #{e.message}"
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
log_sql
|
|
@@ -102,8 +102,17 @@ module PgSlice
|
|
|
102
102
|
else
|
|
103
103
|
fmt = "%Y-%m-%d"
|
|
104
104
|
end
|
|
105
|
-
str =
|
|
106
|
-
add_cast
|
|
105
|
+
str = quote(time.strftime(fmt))
|
|
106
|
+
if add_cast
|
|
107
|
+
case cast
|
|
108
|
+
when "date", "timestamptz"
|
|
109
|
+
"#{str}::#{cast}"
|
|
110
|
+
else
|
|
111
|
+
abort "Invalid cast"
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
str
|
|
115
|
+
end
|
|
107
116
|
end
|
|
108
117
|
|
|
109
118
|
def name_format(period)
|
|
@@ -157,8 +166,12 @@ module PgSlice
|
|
|
157
166
|
PG::Connection.quote_ident(value)
|
|
158
167
|
end
|
|
159
168
|
|
|
160
|
-
def
|
|
161
|
-
|
|
169
|
+
def quote(value)
|
|
170
|
+
if value.is_a?(Numeric)
|
|
171
|
+
value
|
|
172
|
+
else
|
|
173
|
+
connection.escape_literal(value)
|
|
174
|
+
end
|
|
162
175
|
end
|
|
163
176
|
|
|
164
177
|
def quote_table(table)
|
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,14 +155,17 @@ 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
|
-
# legacy
|
|
142
169
|
def fetch_settings(trigger_name)
|
|
143
170
|
needs_comment = false
|
|
144
171
|
trigger_comment = fetch_trigger(trigger_name)
|
|
@@ -166,6 +193,10 @@ module PgSlice
|
|
|
166
193
|
needs_comment = true
|
|
167
194
|
end
|
|
168
195
|
|
|
196
|
+
unless ["date", "timestamptz"].include?(cast)
|
|
197
|
+
abort "Invalid cast"
|
|
198
|
+
end
|
|
199
|
+
|
|
169
200
|
version ||= trigger_comment ? 1 : 2
|
|
170
201
|
declarative = version > 1
|
|
171
202
|
|
|
@@ -174,30 +205,24 @@ module PgSlice
|
|
|
174
205
|
|
|
175
206
|
protected
|
|
176
207
|
|
|
208
|
+
def abort(message)
|
|
209
|
+
PgSlice::CLI.instance.send(:abort, message)
|
|
210
|
+
end
|
|
211
|
+
|
|
177
212
|
def execute(*args)
|
|
178
213
|
PgSlice::CLI.instance.send(:execute, *args)
|
|
179
214
|
end
|
|
180
215
|
|
|
181
|
-
def
|
|
182
|
-
PgSlice::CLI.instance.send(:
|
|
216
|
+
def quote(value)
|
|
217
|
+
PgSlice::CLI.instance.send(:quote, value)
|
|
183
218
|
end
|
|
184
219
|
|
|
185
220
|
def quote_ident(value)
|
|
186
|
-
|
|
221
|
+
PgSlice::CLI.instance.send(:quote_ident, value)
|
|
187
222
|
end
|
|
188
223
|
|
|
189
|
-
def
|
|
190
|
-
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def sql_date(time, cast, add_cast = true)
|
|
194
|
-
if cast == "timestamptz"
|
|
195
|
-
fmt = "%Y-%m-%d %H:%M:%S UTC"
|
|
196
|
-
else
|
|
197
|
-
fmt = "%Y-%m-%d"
|
|
198
|
-
end
|
|
199
|
-
str = escape_literal(time.strftime(fmt))
|
|
200
|
-
add_cast ? "#{str}::#{cast}" : str
|
|
224
|
+
def sql_date(*args)
|
|
225
|
+
PgSlice::CLI.instance.send(:sql_date, *args)
|
|
201
226
|
end
|
|
202
227
|
end
|
|
203
228
|
end
|
data/lib/pgslice/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgslice
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: cgi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: pg
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -77,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
91
|
- !ruby/object:Gem::Version
|
|
78
92
|
version: '0'
|
|
79
93
|
requirements: []
|
|
80
|
-
rubygems_version:
|
|
94
|
+
rubygems_version: 4.0.3
|
|
81
95
|
specification_version: 4
|
|
82
96
|
summary: Postgres partitioning as easy as pie
|
|
83
97
|
test_files: []
|