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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 325b5cd0c6ec6dfa7a98db88a06bb47ed4f8ee327c6c21dddfd897ec13f54533
4
- data.tar.gz: aabbfee0e2bced3b7e93f4d9f0db218db060779a3b75b14ac7056867901bae26
3
+ metadata.gz: 80b19b5f7b8bc83c0e75061952d6b26b1dc6ddba6eec34c1ca6a25ec53050201
4
+ data.tar.gz: b59c9d219b0bc539b5e45c078ff7bdd9bf3df9a3856574f811a19a33052c58bb
5
5
  SHA512:
6
- metadata.gz: ba7310102950041d8247779282b6b252d40d7c824bd03b07d08ed7e400534cbf44eb31161595004ff782ccdaa9eacd0d6698797068ea06109ef974eebab8439b
7
- data.tar.gz: 50faa245fb1ac68b6b685af8529277f23a46146477fd37e9ec52a6984222daa3392ca49e9d5276f03177f755ccec5c94989d4f6f25e182f3013ed9b24a8a7250
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. You can also install it with [Homebrew](#homebrew) or [Docker](#docker). If installation fails, you may need to install [dependencies](#dependencies).
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"."visits_202408" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-08-01') TO ('2024-09-01');
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"."visits_202408" ADD PRIMARY KEY ("id");
115
+ ALTER TABLE "public"."visits_202508" ADD PRIMARY KEY ("id");
114
116
 
115
- CREATE TABLE "public"."visits_202409" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-09-01') TO ('2024-10-01');
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"."visits_202409" ADD PRIMARY KEY ("id");
119
+ ALTER TABLE "public"."visits_202509" ADD PRIMARY KEY ("id");
118
120
 
119
- CREATE TABLE "public"."visits_202410" PARTITION OF "public"."visits_intermediate" FOR VALUES FROM ('2024-10-01') TO ('2024-11-01');
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"."visits_202410" ADD PRIMARY KEY ("id");
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" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
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" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
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" >= '2024-08-01'::date AND "created_at" < '2024-11-01'::date
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"."visits_202408";
154
+ ANALYZE VERBOSE "public"."visits_202508";
153
155
 
154
- ANALYZE VERBOSE "public"."visits_202409";
156
+ ANALYZE VERBOSE "public"."visits_202509";
155
157
 
156
- ANALYZE VERBOSE "public"."visits_202410";
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>_202409 $PGSLICE_URL > <table>_202409.dump
221
- psql -c "DROP TABLE <table>_202409" $PGSLICE_URL
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>_202409.dump s3://<s3-bucket>/<table>_202409.dump
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 >= '2024-09-01' AND created_at < '2024-09-02'
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. Note that Postgres doesn’t support `BEFORE / FOR EACH ROW` triggers on partitioned tables.
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 ankane/brew/pgslice
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 'column:#{field},period:#{period},cast:#{cast}';"
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 << <<-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};
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 << <<-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
+ 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 << <<-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;
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
@@ -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
- analyze_list = parent_table.partitions + [parent_table]
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
@@ -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 = <<-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}
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)
@@ -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 << <<-SQL
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 << <<-SQL
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 #{quote("column:#{column},period:#{period},cast:#{cast},version:#{version}")};
62
59
  SQL
63
60
  else
64
- queries << <<-SQL
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 << <<-SQL
75
- CREATE FUNCTION #{quote_ident(trigger_name)}()
76
- RETURNS trigger AS $$
77
- BEGIN
78
- RAISE EXCEPTION 'Create partitions first.';
79
- END;
80
- $$ LANGUAGE plpgsql;
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 << <<-SQL
84
- CREATE TRIGGER #{quote_ident(trigger_name)}
85
- BEFORE INSERT ON #{quote_table(intermediate_table)}
86
- FOR EACH ROW EXECUTE PROCEDURE #{quote_ident(trigger_name)}();
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 << <<-SQL
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 #{quote("column:#{column},period:#{period},cast:#{cast}")};
92
89
  SQL
93
90
  end
94
91
 
@@ -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 = #{escape_literal(options[:lock_timeout])};")
23
+ queries.unshift("SET LOCAL lock_timeout = #{quote(options[:lock_timeout])};")
24
24
 
25
25
  run_queries(queries)
26
26
  end
@@ -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
@@ -77,7 +77,7 @@ module PgSlice
77
77
  begin
78
78
  execute(query)
79
79
  rescue PG::ServerError => e
80
- abort("#{e.class.name}: #{e.message}")
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 = escape_literal(time.strftime(fmt))
106
- add_cast ? "#{str}::#{cast}" : str
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 escape_literal(value)
161
- connection.escape_literal(value)
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
- execute("SELECT COUNT(*) FROM pg_catalog.pg_tables WHERE schemaname = $1 AND tablename = $2", [schema, name]).first["count"].to_i > 0
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
- execute("SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND is_generated = 'NEVER'", [schema, name]).map { |r| r["column_name"] }
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 = <<-SQL
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
- execute("SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = #{regclass} AND contype ='f'").map { |r| r["pg_get_constraintdef"] }
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 = <<-SQL
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
- execute("SELECT pg_get_indexdef(indexrelid) FROM pg_index WHERE indrelid = #{regclass} AND indisprimary = 'f'").map { |r| r["pg_get_indexdef"] }
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
- data_type = execute("SELECT data_type FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = $3", [schema, name, column])[0]["data_type"]
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
- conditions << "#{quote_ident(primary_key)} <= #{below}" if below
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 = <<-SQL
140
+ query = <<~SQL
117
141
  SELECT
118
- nmsp_child.nspname AS schema,
119
- child.relname AS name
142
+ nmsp_child.nspname AS schema,
143
+ child.relname AS name
120
144
  FROM pg_inherits
121
- JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
122
- JOIN pg_class child ON pg_inherits.inhrelid = child.oid
123
- JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace
124
- JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace
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(#{regclass}) AS comment")[0]
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
- execute("SELECT obj_description(oid, 'pg_trigger') AS comment FROM pg_trigger WHERE tgname = $1 AND tgrelid = #{regclass}", [trigger_name])[0]
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 escape_literal(value)
182
- PgSlice::CLI.instance.send(:escape_literal, value)
216
+ def quote(value)
217
+ PgSlice::CLI.instance.send(:quote, value)
183
218
  end
184
219
 
185
220
  def quote_ident(value)
186
- PG::Connection.quote_ident(value)
221
+ PgSlice::CLI.instance.send(:quote_ident, value)
187
222
  end
188
223
 
189
- def regclass
190
- "#{escape_literal(quote_table)}::regclass"
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
@@ -1,3 +1,3 @@
1
1
  module PgSlice
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.2"
3
3
  end
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.0
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: 3.6.7
94
+ rubygems_version: 4.0.3
81
95
  specification_version: 4
82
96
  summary: Postgres partitioning as easy as pie
83
97
  test_files: []