@@ -57,14 +57,20 @@ CREATE TABLE tables (
57
57
initial_node int NOT NULL REFERENCES nodes(id)
58
58
);
59
59
60
- -- On adding new table, create it on non-owner nodes using provided sql and
61
- -- partition
60
+ -- On adding new table, create this table on non-owner nodes using provided sql
61
+ -- and partition it.
62
62
CREATE FUNCTION new_table_worker_side () RETURNS TRIGGER AS $$
63
63
BEGIN
64
64
IF NEW .initial_node != (SELECT shardman .get_node_id ()) THEN
65
+ EXECUTE format (' DROP TABLE IF EXISTS %I CASCADE;' , NEW .relation );
65
66
EXECUTE format(' %s' , NEW .create_sql );
66
- EXECUTE format(' select create_hash_partitions(%L, %L, %L);' ,
67
- NEW .relation , NEW .expr , NEW .partitions_count );
67
+ -- We are adding '_tmp' to default names, because
68
+ -- these partitions will be immediately replaced with foreign tables
69
+ -- having conventional names.
70
+ EXECUTE format(' select create_hash_partitions(%L, %L, %L, true, %L);' ,
71
+ NEW .relation , NEW .expr , NEW .partitions_count ,
72
+ (SELECT ARRAY(SELECT part_name FROM shardman .gen_part_names (
73
+ NEW .relation , NEW .partitions_count , ' _tmp' ))));
68
74
END IF;
69
75
RETURN NULL ;
70
76
END
@@ -77,13 +83,10 @@ ALTER TABLE shardman.tables ENABLE REPLICA TRIGGER new_table_worker_side;
77
83
CREATE FUNCTION new_table_master_side () RETURNS TRIGGER AS $$
78
84
BEGIN
79
85
INSERT INTO shardman .partitions
80
- -- part names look like tablename_partnum, partnums start from 0
81
- SELECT NEW .relation || ' _' || range .num AS part_name,
82
- NEW .relation AS relation,
83
- NEW .initial_node AS owner
84
- FROM
85
- (SELECT num FROM generate_series(0 , NEW .partitions_count , 1 )
86
- AS range(num)) AS range;
86
+ SELECT part_name, NEW .relation AS relation, NEW .initial_node AS owner
87
+ FROM (SELECT part_name FROM shardman .gen_part_names (
88
+ NEW .relation , NEW .partitions_count ))
89
+ AS partnames;
87
90
RETURN NULL ;
88
91
END
89
92
$$ LANGUAGE plpgsql;
@@ -96,6 +99,99 @@ CREATE TABLE partitions (
96
99
owner int REFERENCES nodes(id) -- node on which partition lies
97
100
);
98
101
102
+ -- On adding new partition, create proper foreign server & foreign table and
103
+ -- replace tmp (empty) partition with it.
104
+ CREATE FUNCTION new_partition () RETURNS TRIGGER AS $$
105
+ DECLARE
106
+ connstring text ;
107
+ connstring_keywords text [];
108
+ connstring_vals text [];
109
+ server_opts text default ' ' ;
110
+ um_opts text default ' ' ;
111
+ server_opts_first_time_through bool DEFAULT true;
112
+ um_opts_first_time_through bool DEFAULT true;
113
+ BEGIN
114
+ IF NEW .owner != (SELECT shardman .get_node_id ()) THEN
115
+ raise info ' creating foreign table' ;
116
+ SELECT nodes .connstring FROM shardman .nodes WHERE id = NEW .owner
117
+ INTO connstring;
118
+ EXECUTE format(' DROP SERVER IF EXISTS %I CASCADE;' , NEW .part_name );
119
+ -- Options to postgres_fdw are specified in two places: user & password
120
+ -- in user mapping and everything else in create server. The problem is
121
+ -- that we use single connstring, however user mapping and server
122
+ -- doesn't understand this format, i.e. we can't say create server
123
+ -- ... options (dbname 'port=4848 host=blabla.org'). So we have to parse
124
+ -- the opts and pass them manually. libpq knows how to do it, but
125
+ -- doesn't expose that. On the other hand, quote_literal (which is
126
+ -- neccessary here) doesn't have handy C API. I resorted to have C
127
+ -- function which parses the opts and returns them in two parallel
128
+ -- arrays, and here we join them with quoting.
129
+ SELECT * FROM shardman .pq_conninfo_parse (connstring)
130
+ INTO connstring_keywords, connstring_vals;
131
+ FOR i IN 1 ..(SELECT array_upper(connstring_keywords, 1 )) LOOP
132
+ IF connstring_keywords[i] = ' client_encoding' OR
133
+ connstring_keywords[i] = ' fallback_application_name' THEN
134
+ CONTINUE; /* not allowed in postgres_fdw */
135
+ ELSIF connstring_keywords[i] = ' user' OR
136
+ connstring_keywords[i] = ' password' THEN -- user mapping option
137
+ IF NOT um_opts_first_time_through THEN
138
+ um_opts := um_opts || ' , ' ;
139
+ END IF;
140
+ um_opts_first_time_through := false;
141
+ um_opts := um_opts ||
142
+ format(' %s %L' , connstring_keywords[i], connstring_vals[i]);
143
+ ELSE -- server option
144
+ IF NOT server_opts_first_time_through THEN
145
+ server_opts := server_opts || ' , ' ;
146
+ END IF;
147
+ server_opts_first_time_through := false;
148
+ server_opts := server_opts ||
149
+ format(' %s %L' , connstring_keywords[i], connstring_vals[i]);
150
+ END IF;
151
+ END LOOP;
152
+ -- OPTIONS () is syntax error, so add OPTIONS only if we really have opts
153
+ IF server_opts != ' ' THEN
154
+ server_opts := format(' OPTIONS (%s)' , server_opts);
155
+ END IF;
156
+ IF um_opts != ' ' THEN
157
+ um_opts := format(' OPTIONS (%s)' , um_opts);
158
+ END IF;
159
+ raise log ' serv opts are %, um opts are %' , server_opts, um_opts;
160
+ EXECUTE format(' CREATE SERVER %I FOREIGN DATA WRAPPER
161
+ postgres_fdw %s;' , NEW .part_name , server_opts);
162
+ EXECUTE format(' DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER %I;' ,
163
+ NEW .part_name );
164
+ EXECUTE format(' CREATE USER MAPPING FOR CURRENT_USER SERVER %I
165
+ %s;' , NEW .part_name , um_opts);
166
+ EXECUTE format(' DROP FOREIGN TABLE IF EXISTS %I;' , NEW .part_name );
167
+
168
+ -- Generate and execute CREATE FOREIGN TABLE sql statement which will
169
+ -- clone the existing local table schema. In constrast to
170
+ -- gen_create_table_sql, here we need only the header of the table,
171
+ -- i.e. its attributes. CHECK constraint for partition will be added
172
+ -- during the attachment, and other stuff doesn't seem to have much
173
+ -- sense on foreign table.
174
+ -- In fact, we should have CREATE FOREIGN TABLE (LIKE ...) to make this
175
+ -- sane. We could also used here IMPORT FOREIGN SCHEMA, but it
176
+ -- unneccessary involves network (we already have this schema locally)
177
+ -- and dangerous: what if table was created and dropped before this
178
+ -- change reached us?
179
+ EXECUTE format(' CREATE FOREIGN TABLE %I %s SERVER %I' ,
180
+ NEW .part_name ,
181
+ (select
182
+ shardman .reconstruct_table_attrs (' partitioned_table_0_tmp' )),
183
+ NEW .part_name );
184
+ -- Finally, replace empty local tmp partition with foreign table
185
+ EXECUTE format(' SELECT replace_hash_partition(%L, %L)' ,
186
+ format(' %s_tmp' , NEW .part_name ), NEW .part_name );
187
+ END IF;
188
+ RETURN NULL ;
189
+ END
190
+ $$ LANGUAGE plpgsql;
191
+ CREATE TRIGGER new_partition AFTER INSERT ON shardman .partitions
192
+ FOR EACH ROW EXECUTE PROCEDURE new_partition();
193
+ -- fire trigger only on worker nodes
194
+ ALTER TABLE shardman .partitions ENABLE REPLICA TRIGGER new_partition;
99
195
100
196
-- Currently it is used just to store node id, in general we can keep any local
101
197
-- node metadata here. If is ever used extensively, probably hstore suits better.
@@ -132,7 +228,6 @@ CREATE TRIGGER cmd_log_inserts
132
228
FOR EACH STATEMENT
133
229
EXECUTE PROCEDURE notify_shardmaster();
134
230
135
-
136
231
-- probably better to keep opts in an array field, but working with arrays from
137
232
-- libpq is not very handy
138
233
-- opts must be inserted sequentially, we order by them by id
@@ -182,7 +277,7 @@ CREATE FUNCTION create_meta_pub() RETURNS void AS $$
182
277
BEGIN
183
278
IF NOT EXISTS (SELECT * FROM pg_publication WHERE pubname = ' shardman_meta_pub' ) THEN
184
279
CREATE PUBLICATION shardman_meta_pub FOR TABLE
185
- shardman .nodes , shardman .tables ;
280
+ shardman .nodes , shardman .tables , shardman . partitions ;
186
281
END IF;
187
282
END;
188
283
$$ LANGUAGE plpgsql;
@@ -302,9 +397,27 @@ BEGIN
302
397
END
303
398
$$ LANGUAGE plpgsql;
304
399
400
+ -- generate one-column table with partition names as 'tablename'_'partnum''suffix'
401
+ CREATE FUNCTION gen_part_names (relation text , partitions_count int ,
402
+ suffix text DEFAULT ' ' )
403
+ RETURNS TABLE(part_name text ) AS $$
404
+ BEGIN
405
+ RETURN QUERY SELECT relation || ' _' || range .num || suffix AS partname
406
+ FROM
407
+ (SELECT num FROM generate_series(0 , partitions_count - 1 , 1 )
408
+ AS range(num)) AS range;
409
+ END
410
+ $$ LANGUAGE plpgsql;
411
+
305
412
CREATE FUNCTION gen_create_table_sql (relation text , connstring text ) RETURNS text
306
413
AS ' pg_shardman' LANGUAGE C;
307
414
415
+ CREATE FUNCTION reconstruct_table_attrs (relation regclass)
416
+ RETURNS text AS ' pg_shardman' LANGUAGE C STRICT;
417
+
418
+ CREATE FUNCTION pq_conninfo_parse (IN conninfo text , OUT keys text [], OUT vals text [])
419
+ RETURNS record AS ' pg_shardman' LANGUAGE C STRICT;
420
+
308
421
-- Interface functions
309
422
310
423
-- Add a node. Its state will be reset, all shardman data lost.
0 commit comments