1use crate::models::PostgresDatabase;
2use crate::*;
3use std::sync::Arc;
4
5mod data_format;
6mod elefant_file;
7mod postgres;
8mod sql_file;
9mod table_data;
10
11use crate::models::PostgresSchema;
13use crate::models::PostgresTable;
14use crate::quoting::IdentifierQuoter;
15pub use data_format::*;
16pub use postgres::PostgresInstanceStorage;
17pub use sql_file::{apply_sql_file, apply_sql_string, SqlDataMode, SqlFile, SqlFileOptions};
18pub use table_data::*;
19
20pub trait BaseCopyTarget {
22 fn supported_data_format(&self) -> impl std::future::Future<Output = Result<Vec<DataFormat>>>;
24}
25
26pub trait CopySourceFactory: BaseCopyTarget {
28 type SequentialSource: CopySource;
31
32 type ParallelSource: CopySource + Clone;
35
36 fn get_introspection(&self) -> impl std::future::Future<Output = Result<PostgresDatabase>>;
39
40 fn create_source(
42 &self,
43 ) -> impl std::future::Future<
44 Output = Result<SequentialOrParallel<Self::SequentialSource, Self::ParallelSource>>,
45 >;
46
47 fn create_sequential_source(
49 &self,
50 ) -> impl std::future::Future<Output = Result<Self::SequentialSource>>;
51
52 fn supported_parallelism(&self) -> SupportedParallelism;
55}
56
57pub trait CopySource: Send {
59 type DataReader<'a>: TableDataReader + 'a
61 where
62 Self: 'a;
63
64 type Cleanup: AsyncCleanup;
66
67 fn get_data<'a>(
69 &'a mut self,
70 schema: &'a PostgresSchema,
71 table: &'a PostgresTable,
72 data_format: &'a DataFormat,
73 ) -> impl std::future::Future<Output = Result<TableData<Self::DataReader<'a>, Self::Cleanup>>> + 'a;
74}
75
76pub trait CopyDestinationFactory<'a>: BaseCopyTarget {
78 type SequentialDestination: CopyDestination;
81
82 type ParallelDestination: CopyDestination + Clone;
85
86 fn create_destination(
88 &'a mut self,
89 ) -> impl std::future::Future<
90 Output = Result<
91 SequentialOrParallel<Self::SequentialDestination, Self::ParallelDestination>,
92 >,
93 >;
94
95 fn create_sequential_destination(
97 &'a mut self,
98 ) -> impl std::future::Future<Output = Result<Self::SequentialDestination>>;
99
100 fn supported_parallelism(&self) -> SupportedParallelism;
103}
104
105pub trait CopyTransaction: Send {
109 fn apply_statement(&mut self, statement: &str)
111 -> impl std::future::Future<Output = Result<()>>;
112
113 fn commit(self) -> impl std::future::Future<Output = Result<()>>;
115}
116
117pub trait CopyDestination: Send {
118 type Transaction<'a>: CopyTransaction + 'a
120 where
121 Self: 'a;
122
123 fn apply_data<R: TableDataReader, C: AsyncCleanup>(
126 &mut self,
127 schema: &PostgresSchema,
128 table: &PostgresTable,
129 data: TableData<R, C>,
130 ) -> impl std::future::Future<Output = Result<()>>;
131
132 fn apply_non_transactional_statement(
135 &mut self,
136 statement: &str,
137 ) -> impl std::future::Future<Output = Result<()>>;
138
139 fn begin_transaction(
141 &mut self,
142 ) -> impl std::future::Future<Output = Result<Self::Transaction<'_>>>;
143
144 fn get_identifier_quoter(&self) -> Arc<IdentifierQuoter>;
147
148 fn finish(&mut self) -> impl std::future::Future<Output = Result<()>> {
149 async { Ok(()) }
150 }
151
152 fn try_introspect(
155 &self,
156 ) -> impl std::future::Future<Output = Result<Option<PostgresDatabase>>> {
157 async { Ok(None) }
158 }
159
160 fn has_data_in_table(
161 &self,
162 _schema: &PostgresSchema,
163 _table: &PostgresTable,
164 ) -> impl std::future::Future<Output = Result<bool>> {
165 async { Ok(false) }
166 }
167}
168
169pub enum SequentialOrParallel<S: Send, P: Send + Clone> {
171 Sequential(S),
172 Parallel(P),
173}
174
175#[derive(Clone, Debug, Eq, PartialEq)]
177pub enum SupportedParallelism {
178 Sequential,
180 Parallel,
182}
183
184impl SupportedParallelism {
185 pub fn negotiate_parallelism(&self, other: SupportedParallelism) -> SupportedParallelism {
187 match (self, other) {
188 (SupportedParallelism::Parallel, SupportedParallelism::Parallel) => {
189 SupportedParallelism::Parallel
190 }
191 _ => SupportedParallelism::Sequential,
192 }
193 }
194}
195
196impl<S: CopyDestination, P: CopyDestination + Clone> SequentialOrParallel<S, P> {
197 pub(crate) async fn finish(&mut self) -> Result<()> {
198 with_both!(self, |d| d.finish().await)
199 }
200
201 pub(crate) async fn try_get_introspeciton(&self) -> Result<Option<PostgresDatabase>> {
202 with_both!(self, |d| d.try_introspect().await)
203 }
204}
205
206#[derive(Copy, Clone)]
210pub struct ParallelCopyDestinationNotAvailable {
211 _private: (),
212}
213
214pub struct ParallelCopyTransactionNotAvailable {
216 _private: (),
217}
218
219impl CopyTransaction for ParallelCopyTransactionNotAvailable {
220 async fn apply_statement(&mut self, _statement: &str) -> Result<()> {
221 unreachable!("Parallel copy destination not available")
222 }
223
224 async fn commit(self) -> Result<()> {
225 unreachable!("Parallel copy destination not available")
226 }
227}
228
229impl CopyDestination for ParallelCopyDestinationNotAvailable {
230 type Transaction<'a> = ParallelCopyTransactionNotAvailable;
231
232 async fn apply_data<R: TableDataReader, C: AsyncCleanup>(
233 &mut self,
234 _schema: &PostgresSchema,
235 _table: &PostgresTable,
236 _data: TableData<R, C>,
237 ) -> Result<()> {
238 unreachable!("Parallel copy destination not available")
239 }
240
241 async fn apply_non_transactional_statement(&mut self, _statement: &str) -> Result<()> {
242 unreachable!("Parallel copy destination not available")
243 }
244
245 async fn begin_transaction(&mut self) -> Result<ParallelCopyTransactionNotAvailable> {
246 unreachable!("Parallel copy destination not available")
247 }
248
249 fn get_identifier_quoter(&self) -> Arc<IdentifierQuoter> {
250 unreachable!("Parallel copy destination not available")
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use crate::test_helpers::TestHelper;
257
258 pub fn get_copy_source_database_create_script(version: i32) -> &'static str {
259 if version >= 150 {
260 r#"
261 create extension btree_gin;
262
263 create table people(
264 id serial primary key,
265 name text not null unique,
266 age int not null check (age > 0),
267 constraint multi_check check (name != 'fsgsdfgsdf' and age < 9999)
268 );
269
270 create index people_age_idx on people (age desc) include (name, id) where (age % 2 = 0);
271 create index people_age_brin_idx on people using brin (age);
272 create index people_name_lower_idx on people (lower(name));
273
274 insert into people(name, age)
275 values
276 ('foo', 42),
277 ('bar', 89),
278 ('nice', 69),
279 (E'str\nange', 420),
280 (E't\t\tap', 421),
281 (E'q''t', 12)
282 ;
283
284 create table field(
285 id serial primary key
286 );
287
288 create table tree_node(
289 id serial primary key,
290 field_id int not null references field(id),
291 name text not null,
292 parent_id int,
293 constraint field_id_id_unique unique (field_id, id),
294 foreign key (field_id, parent_id) references tree_node(field_id, id),
295 constraint unique_name_per_level unique nulls not distinct (field_id, parent_id, name)
296 );
297
298 create view people_who_cant_drink as select * from people where age < 18;
299
300 create table ext_test_table(
301 id serial primary key,
302 name text not null,
303 search_vector tsvector generated always as (to_tsvector('english', name)) stored
304 );
305
306 create index ext_test_table_name_idx on ext_test_table using gin (id, search_vector);
307
308 create table array_test(
309 name text[] not null
310 );
311
312 insert into array_test(name)
313 values
314 ('{"foo", "bar"}'),
315 ('{"baz", "qux"}'),
316 ('{"quux", "corge"}');
317
318 create table my_partitioned_table(
319 value int not null
320 ) partition by range (value);
321
322 create table my_partitioned_table_1 partition of my_partitioned_table for values from (1) to (10);
323 create table my_partitioned_table_2 partition of my_partitioned_table for values from (10) to (20);
324
325 insert into my_partitioned_table(value)
326 values (1), (9), (11), (19);
327
328 create table pets (
329 id serial primary key,
330 name text not null check(length(name) > 1)
331 );
332
333 create table dogs(
334 breed text not null check(length(breed) > 1)
335 ) inherits (pets);
336
337 create table cats(
338 color text not null
339 ) inherits (pets);
340
341 insert into dogs(name, breed) values('Fido', 'beagle');
342 insert into cats(name, color) values('Fluffy', 'white');
343 insert into pets(name) values('Remy');
344 "#
345 } else {
346 r#"
347 create extension btree_gin;
348
349 create table people(
350 id serial primary key,
351 name text not null unique,
352 age int not null check (age > 0),
353 constraint multi_check check (name != 'fsgsdfgsdf' and age < 9999)
354 );
355
356 create index people_age_idx on people (age desc) include (name, id) where (age % 2 = 0);
357 create index people_age_brin_idx on people using brin (age);
358 create index people_name_lower_idx on people (lower(name));
359
360 insert into people(name, age)
361 values
362 ('foo', 42),
363 ('bar', 89),
364 ('nice', 69),
365 (E'str\nange', 420),
366 (E't\t\tap', 421),
367 (E'q''t', 12)
368 ;
369
370 create table field(
371 id serial primary key
372 );
373
374 create table tree_node(
375 id serial primary key,
376 field_id int not null references field(id),
377 name text not null,
378 parent_id int,
379 constraint field_id_id_unique unique (field_id, id),
380 foreign key (field_id, parent_id) references tree_node(field_id, id),
381 constraint unique_name_per_level unique (field_id, parent_id, name)
382 );
383
384 create view people_who_cant_drink as select * from people where age < 18;
385
386 create table ext_test_table(
387 id serial primary key,
388 name text not null,
389 search_vector tsvector generated always as (to_tsvector('english', name)) stored
390 );
391
392 create index ext_test_table_name_idx on ext_test_table using gin (id, search_vector);
393
394 create table array_test(
395 name text[] not null
396 );
397
398 insert into array_test(name)
399 values
400 ('{"foo", "bar"}'),
401 ('{"baz", "qux"}'),
402 ('{"quux", "corge"}');
403
404 create table my_partitioned_table(
405 value int not null
406 ) partition by range (value);
407
408 create table my_partitioned_table_1 partition of my_partitioned_table for values from (1) to (10);
409 create table my_partitioned_table_2 partition of my_partitioned_table for values from (10) to (20);
410
411 insert into my_partitioned_table(value)
412 values (1), (9), (11), (19);
413
414 create table pets (
415 id serial primary key,
416 name text not null check(length(name) > 1)
417 );
418
419 create table dogs(
420 breed text not null check(length(breed) > 1)
421 ) inherits (pets);
422
423 create table cats(
424 color text not null
425 ) inherits (pets);
426
427 insert into dogs(name, breed) values('Fido', 'beagle');
428 insert into cats(name, color) values('Fluffy', 'white');
429 insert into pets(name) values('Remy');
430 "#
431 }
432 }
433
434 pub fn get_expected_people_data() -> Vec<(i32, String, i32)> {
435 vec![
436 (1, "foo".to_string(), 42),
437 (2, "bar".to_string(), 89),
438 (3, "nice".to_string(), 69),
439 (4, "str\nange".to_string(), 420),
440 (5, "t\t\tap".to_string(), 421),
441 (6, "q't".to_string(), 12),
442 ]
443 }
444
445 pub fn get_expected_array_test_data() -> Vec<(Vec<String>,)> {
446 vec![
447 (vec!["foo".to_string(), "bar".to_string()],),
448 (vec!["baz".to_string(), "qux".to_string()],),
449 (vec!["quux".to_string(), "corge".to_string()],),
450 ]
451 }
452
453 pub async fn validate_pets(connection: &TestHelper) {
454 let pets = connection
455 .get_results::<(i32, String)>("select id, name from pets order by id")
456 .await;
457 assert_eq!(
458 pets,
459 vec![
460 (1, "Fido".to_string()),
461 (2, "Fluffy".to_string()),
462 (3, "Remy".to_string()),
463 ]
464 );
465
466 let dogs = connection
467 .get_results::<(i32, String, String)>("select id, name, breed from dogs order by id")
468 .await;
469 assert_eq!(dogs, vec![(1, "Fido".to_string(), "beagle".to_string()),]);
470
471 let cats = connection
472 .get_results::<(i32, String, String)>("select id, name, color from cats order by id")
473 .await;
474 assert_eq!(cats, vec![(2, "Fluffy".to_string(), "white".to_string()),]);
475 }
476
477 pub async fn validate_copy_state(destination: &TestHelper) {
478 let items = destination
479 .get_results::<(i32, String, i32)>("select id, name, age from people;")
480 .await;
481
482 assert_eq!(items, get_expected_people_data());
483
484 let result = destination
485 .get_conn()
486 .execute_non_query("insert into people (name, age) values ('new-value', 10000)")
487 .await;
488 assert!(result.is_err(), "Expected CHECK_VIOLATION error");
489 let err_msg = format!("{}", result.unwrap_err());
490 assert!(
491 err_msg.contains("check") || err_msg.contains("CHECK") || err_msg.contains("violates"),
492 "Expected check violation, got: {err_msg}"
493 );
494
495 let result = destination
496 .get_conn()
497 .execute_non_query("insert into people (name, age) values ('foo', 100)")
498 .await;
499 assert!(result.is_err(), "Expected UNIQUE_VIOLATION error");
500 let err_msg = format!("{}", result.unwrap_err());
501 assert!(
502 err_msg.contains("unique")
503 || err_msg.contains("UNIQUE")
504 || err_msg.contains("duplicate"),
505 "Expected unique violation, got: {err_msg}"
506 );
507
508 destination
509 .execute_not_query("insert into field (id) values (1);")
510 .await;
511
512 destination.execute_not_query("insert into tree_node(id, field_id, name, parent_id) values (1, 1, 'foo', null), (2, 1, 'bar', 1)").await;
513 if destination.get_conn().version() >= 150 {
514 let result = destination.get_conn().execute_non_query("insert into tree_node(id, field_id, name, parent_id) values (3, 1, 'foo', null)").await;
515 assert!(result.is_err(), "Expected UNIQUE_VIOLATION error");
516 }
517
518 let result = destination.get_conn().execute_non_query("insert into tree_node(id, field_id, name, parent_id) values (9999, 9999, 'foobarbaz', null)").await;
519 assert!(result.is_err(), "Expected FOREIGN_KEY_VIOLATION error");
520
521 let people_who_cant_drink = destination
522 .get_results::<(i32, String, i32)>("select id, name, age from people_who_cant_drink;")
523 .await;
524 assert_eq!(people_who_cant_drink, vec![(6, "q't".to_string(), 12)]);
525
526 let array_test_data = destination
527 .get_results::<(Vec<String>,)>("select name from array_test;")
528 .await;
529
530 assert_eq!(array_test_data, get_expected_array_test_data());
531
532 let partition_test_data = destination
533 .get_results::<(i32,)>("select value from my_partitioned_table order by value;")
534 .await;
535
536 assert_eq!(partition_test_data, vec![(1,), (9,), (11,), (19,)]);
537
538 validate_pets(destination).await;
539 }
540}