Repository: datanymizer/datanymizer (language: rust)

Issue:
Feature Request: wildcards in filters
Currently, I have 100s of tables in my database, all under different schemas. 
I want to anonymize around 5 - 10 of my tables. I am able to achieve this using the following config
```yaml
.....
filter:
  data:
    only:
      - "default$default.Profile"
      ...
```
Potentially, this creates another issue as it copies over the other schema, which I don't want to copy.

When I try to use the following config:
```yaml
.....
filter:
  schema:
    only:
      - "default$default.Profile"
      ...
```
Only the tables selected get copy but not their dependencies, for e.g. ENUMs used in those tables.

So I would like to request for a feature in which filter accepts wildcard `*` for anything under a schema.
something like:
```yaml
filter:
  schema:
    only:
      - "default$default.*"
    except:
      - "public.*"
```

Thank you in advance. 


Discussion / hints:
Hello!

We will try to implement something that solves this issue.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 048a707e..56232c2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 ### 🚀 Added
+- Add wildcards support in the filter section [#151](https://github.com/datanymizer/datanymizer/pull/151)
+  ([@evgeniy-r](https://github.com/evgeniy-r))
 
 ### ⚙️ Changed
 
diff --git a/Cargo.lock b/Cargo.lock
index 9b44fd7a..6ae4d730 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -340,6 +340,7 @@ dependencies = [
  "tera",
  "thiserror",
  "unicode-segmentation",
+ "wildmatch",
 ]
 
 [[package]]
@@ -1867,6 +1868,12 @@ version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
+[[package]]
+name = "wildmatch"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/README.md b/README.md
index 60a2d86e..1680193f 100644
--- a/README.md
+++ b/README.md
@@ -229,6 +229,11 @@ filter:
       - public.markets
 ```
 
+You can use wildcards in the `filter` section:
+
+* `?` matches exactly one occurrence of any character;
+* `*` matches arbitrary many (including zero) occurrences of any character.
+
 ### Dump conditions and limit
 
 You can specify conditions (SQL `WHERE` statement) and limit for dumped data per table:
diff --git a/datanymizer_dumper/src/lib.rs b/datanymizer_dumper/src/lib.rs
index 808e9652..2ce3f551 100644
--- a/datanymizer_dumper/src/lib.rs
+++ b/datanymizer_dumper/src/lib.rs
@@ -1,6 +1,6 @@
 use anyhow::Result;
 use core::iter::Iterator;
-use datanymizer_engine::{Filter, Settings};
+use datanymizer_engine::Settings;
 use indicatif::HumanDuration;
 use solvent::DepGraph;
 use std::{collections::HashMap, hash::Hash, time::Instant};
@@ -34,17 +34,17 @@ pub trait Dumper: 'static + Sized + Send {
     /// This stage makes dump foreign keys, indices and other...
     fn post_data(&mut self, _connection: &mut Self::Connection) -> Result<()>;
 
-    fn filter_table(&mut self, table: String, filter: &Option<Filter>) -> bool {
-        if let Some(f) = filter {
-            f.filter_schema(&table) && f.filter_data(&table)
-        } else {
-            true
-        }
+    fn filter_table(&self, table: String) -> bool {
+        self.settings()
+            .filter
+            .as_ref()
+            .map(|f| f.filter_table(&table))
+            .unwrap_or(true)
     }
 
     fn schema_inspector(&self) -> Self::SchemaInspector;
 
-    fn settings(&mut self) -> Settings;
+    fn settings(&self) -> &Settings;
 
     fn write_log(&mut self, message: String) -> Result<()>;
 
diff --git a/datanymizer_dumper/src/postgres/dumper.rs b/datanymizer_dumper/src/postgres/dumper.rs
index f32a8946..7176bbaf 100644
--- a/datanymizer_dumper/src/postgres/dumper.rs
+++ b/datanymizer_dumper/src/postgres/dumper.rs
@@ -20,6 +20,7 @@ pub struct PgDumper<W: Write + Send, I: Indicator + Send> {
     dump_isolation_level: Option<IsolationLevel>,
     pg_dump_location: String,
     pg_dump_args: Vec<String>,
+    tables: Vec<PgTable>,
 }
 
 impl<W: 'static + Write + Send, I: 'static + Indicator + Send> PgDumper<W, I> {
@@ -39,6 +40,7 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> PgDumper<W, I> {
             pg_dump_location,
             schema_inspector: PgSchemaInspector {},
             pg_dump_args,
+            tables: Vec::new(),
         })
     }
 
@@ -74,7 +76,6 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> PgDumper<W, I> {
     }
 
     fn dump_table(&mut self, table: &PgTable, qw: &mut QueryWrapper) -> Result<()> {
-        let settings = self.settings();
         let started = Instant::now();
 
         self.write_log(format!("Dump table: {}", &table.get_full_name()))?;
@@ -83,7 +84,7 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> PgDumper<W, I> {
         self.dump_writer.write_all(table.query_from().as_bytes())?;
         self.dump_writer.write_all(b"\n")?;
 
-        let cfg = settings.find_table(&table.get_names());
+        let cfg = self.engine.settings.find_table(&table.get_names());
 
         self.indicator
             .start_pb(table.count_of_query_to(cfg), &table.get_full_name());
@@ -141,27 +142,32 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> Dumper for PgDump
 
     // Stage before dumping data. It makes dump schema with any options
     fn pre_data(&mut self, connection: &mut Self::Connection) -> Result<()> {
+        self.debug("Fetch tables metadata...".into());
+        let mut tables = self.schema_inspector().ordered_tables(connection);
+
+        sort_tables(
+            &mut tables,
+            self.engine.settings.table_order.as_ref().unwrap_or(&vec![]),
+        );
+        self.tables = tables.into_iter().map(|(t, _)| t).collect();
+
+        if let Some(filter) = &mut self.engine.settings.filter {
+            filter.load_tables(self.tables.iter().map(|t| t.get_full_name()).collect());
+        }
+
         self.debug("Prepare data scheme...".into());
         self.run_pg_dump("pre-data", connection.url.as_str())
     }
 
     // This stage makes dump data only
     fn data(&mut self, connection: &mut Self::Connection) -> Result<()> {
-        let settings = self.settings();
         self.write_log("Start dumping data".into())?;
-        self.debug("Fetch tables metadata...".into());
 
-        let mut tables = self.schema_inspector().ordered_tables(connection);
-        sort_tables(
-            &mut tables,
-            settings.table_order.as_ref().unwrap_or(&vec![]),
-        );
-
-        let all_tables_count = tables.len();
+        let all_tables_count = self.tables.len();
 
         let mut query_wrapper =
             QueryWrapper::with_isolation_level(&mut connection.client, self.dump_isolation_level)?;
-        for (ind, (table, _weight)) in tables.iter().enumerate() {
+        for (ind, table) in self.tables.clone().iter().enumerate() {
             self.debug(format!(
                 "[{} / {}] Prepare to dump table: {}",
                 ind + 1,
@@ -169,7 +175,7 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> Dumper for PgDump
                 table.get_full_name(),
             ));
 
-            if self.filter_table(table.get_full_name(), &settings.filter) {
+            if self.filter_table(table.get_full_name()) {
                 self.dump_table(table, &mut query_wrapper)?;
             } else {
                 self.debug(format!("[Dumping: {}] --- SKIP ---", table.get_full_name()));
@@ -190,8 +196,8 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> Dumper for PgDump
         self.schema_inspector.clone()
     }
 
-    fn settings(&mut self) -> Settings {
-        self.engine.settings.clone()
+    fn settings(&self) -> &Settings {
+        &self.engine.settings
     }
 
     fn write_log(&mut self, message: String) -> Result<()> {
@@ -208,15 +214,14 @@ impl<W: 'static + Write + Send, I: 'static + Indicator + Send> Dumper for PgDump
 fn table_args(filter: &Option<Filter>) -> Result<Vec<String>> {
     let mut args = vec![];
     if let Some(f) = filter {
-        if let Some(list) = &f.schema {
-            let flag = match list {
-                TableList::Only(_) => "-t",
-                TableList::Except(_) => "-T",
-            };
-            for table in list.tables() {
-                args.push(String::from(flag));
-                args.push(PgTable::quote_table_name(table.as_str())?);
-            }
+        let list = f.schema_match_list();
+        let flag = match list {
+            TableList::Only(_) => "-t",
+            TableList::Except(_) => "-T",
+        };
+        for table in list.tables() {
+            args.push(String::from(flag));
+            args.push(PgTable::quote_table_name(table.as_str())?);
         }
     }
 
@@ -236,31 +241,48 @@ mod tests {
 
     #[test]
     fn test_table_args() {
+        let tables = vec![String::from("table1"), String::from("table2")];
+
         let empty: Vec<String> = vec![];
         assert_eq!(table_args(&None).unwrap(), empty);
 
-        let filter = Filter {
-            schema: Some(TableList::Except(vec![String::from("table1")])),
-            data: None,
-        };
+        let mut filter = Filter::new(
+            TableList::Except(vec![String::from("table1")]),
+            TableList::default(),
+        );
+        filter.load_tables(tables.clone());
         assert_eq!(
             table_args(&Some(filter)).unwrap(),
             vec![String::from("-T"), String::from("\"table1\"")]
         );
 
-        let filter = Filter {
-            schema: None,
-            data: Some(TableList::Except(vec![String::from("table1")])),
-        };
+        let mut filter = Filter::new(
+            TableList::default(),
+            TableList::Except(vec![String::from("table1")]),
+        );
+        filter.load_tables(tables.clone());
         assert_eq!(table_args(&Some(filter)).unwrap(), empty);
 
-        let filter = Filter {
-            schema: Some(TableList::Only(vec![
-                String::from("table1"),
-                String::from("table2"),
-            ])),
-            data: None,
-        };
+        let mut filter = Filter::new(
+            TableList::Only(vec![String::from("table1"), String::from("table2")]),
+            TableList::default(),
+        );
+        filter.load_tables(tables.clone());
+        assert_eq!(
+            table_args(&Some(filter)).unwrap(),
+            vec![
+                String::from("-t"),
+                String::from("\"table1\""),
+                String::from("-t"),
+                String::from("\"table2\"")
+            ]
+        );
+
+        let mut filter = Filter::new(
+            TableList::Only(vec![String::from("table*")]),
+            TableList::default(),
+        );
+        filter.load_tables(tables);
         assert_eq!(
             table_args(&Some(filter)).unwrap(),
             vec![
diff --git a/datanymizer_engine/Cargo.toml b/datanymizer_engine/Cargo.toml
index 1888353f..7846f3f8 100644
--- a/datanymizer_engine/Cargo.toml
+++ b/datanymizer_engine/Cargo.toml
@@ -8,15 +8,16 @@ edition = "2021"
 
 [dependencies]
 anyhow = "1.0"
+chrono = "0.4"
 config = "0.10"
 csv = "1.1"
 fake = { version = "2.4.1", features = ["chrono"] }
-serde = { version = "1.0", features = ["derive"] }
+once_cell = "1.5.2"
 rand = "0.8.4"
-unicode-segmentation = "1.7.0"
-serde_yaml = "0.8.14"
+serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
+serde_yaml = "0.8.14"
 tera = "1.15.0"
-chrono = "0.4"
-once_cell = "1.5.2"
 thiserror = "1.0"
+unicode-segmentation = "1.7.0"
+wildmatch = "2.1.0"
diff --git a/datanymizer_engine/src/settings/filter.rs b/datanymizer_engine/src/settings/filter.rs
index 2454afb8..c6485ff2 100644
--- a/datanymizer_engine/src/settings/filter.rs
+++ b/datanymizer_engine/src/settings/filter.rs
@@ -1,11 +1,14 @@
 use serde::Deserialize;
+use wildmatch::WildMatch;
 
 /// Filter for include or exclude tables
 #[derive(Clone, Debug, Deserialize, PartialEq)]
 #[serde(from = "Config")]
 pub struct Filter {
-    pub schema: Option<TableList>,
-    pub data: Option<TableList>,
+    schema: TableList,
+    matched_schema: TableList,
+    data: TableList,
+    matched_data: TableList,
 }
 
 impl From<Config> for Filter {
@@ -19,28 +22,34 @@ impl From<Config> for Filter {
 
 impl From<FullConfig> for Filter {
     fn from(full_config: FullConfig) -> Self {
-        Self {
-            schema: full_config.schema,
-            data: full_config.data,
-        }
+        Self::new(full_config.schema, full_config.data)
     }
 }
 
 impl Filter {
-    pub fn filter_schema(&self, table: &str) -> bool {
-        Self::filter(&self.schema, table)
+    pub fn new(schema: TableList, data: TableList) -> Self {
+        let matched_schema = TableList::default();
+        let matched_data = TableList::default();
+
+        Self {
+            schema,
+            matched_schema,
+            data,
+            matched_data,
+        }
     }
 
-    pub fn filter_data(&self, table: &str) -> bool {
-        Self::filter(&self.data, table)
+    pub fn load_tables(&mut self, tables: Vec<String>) {
+        self.matched_schema = self.schema.match_with_wildcards(tables.clone());
+        self.matched_data = self.data.match_with_wildcards(tables);
     }
 
-    fn filter(list: &Option<TableList>, table: &str) -> bool {
-        if let Some(l) = list {
-            l.filter(table)
-        } else {
-            true
-        }
+    pub fn schema_match_list(&self) -> &TableList {
+        &self.matched_schema
+    }
+
+    pub fn filter_table(&self, table: &str) -> bool {
+        self.matched_schema.filter(table) && self.matched_data.filter(table)
     }
 }
 
@@ -52,20 +61,43 @@ pub enum TableList {
     Except(Vec<String>),
 }
 
-impl TableList {
-    pub fn filter(&self, table: &str) -> bool {
-        match self {
-            Self::Only(tables) => tables.iter().any(|t| t == table),
-            Self::Except(tables) => !tables.iter().any(|t| t == table),
-        }
+impl Default for TableList {
+    fn default() -> Self {
+        Self::Except(Vec::new())
     }
+}
 
+impl TableList {
     pub fn tables(&self) -> &Vec<String> {
         match self {
             Self::Only(tables) => tables,
             Self::Except(tables) => tables,
         }
     }
+
+    fn match_with_wildcards(&self, tables: Vec<String>) -> Self {
+        let matchers: Vec<_> = self
+            .tables()
+            .iter()
+            .map(|t| WildMatch::new(t.as_str()))
+            .collect();
+        let matched_tables = tables
+            .into_iter()
+            .filter(|t| matchers.iter().any(|m| m.matches(t)))
+            .collect();
+
+        match self {
+            Self::Only(_) => Self::Only(matched_tables),
+            Self::Except(_) => Self::Except(matched_tables),
+        }
+    }
+
+    fn filter(&self, table: &str) -> bool {
+        match self {
+            Self::Only(tables) => tables.iter().any(|t| t == table),
+            Self::Except(tables) => !tables.iter().any(|t| t == table),
+        }
+    }
 }
 
 #[derive(Deserialize)]
@@ -77,15 +109,17 @@ enum Config {
 
 #[derive(Deserialize)]
 struct FullConfig {
-    schema: Option<TableList>,
-    data: Option<TableList>,
+    #[serde(default)]
+    schema: TableList,
+    #[serde(default)]
+    data: TableList,
 }
 
 impl From<TableList> for FullConfig {
     fn from(list: TableList) -> Self {
         Self {
-            schema: None,
-            data: Some(list),
+            schema: TableList::default(),
+            data: list,
         }
     }
 }
@@ -193,13 +227,10 @@ mod tests {
                 "#;
             assert_eq!(
                 deserialize(config),
-                Filter {
-                    schema: None,
-                    data: Some(TableList::Only(vec![
-                        String::from("table1"),
-                        String::from("table2")
-                    ])),
-                }
+                Filter::new(
+                    TableList::default(),
+                    TableList::Only(vec![String::from("table1"), String::from("table2")])
+                )
             );
         }
 
@@ -214,13 +245,10 @@ mod tests {
 
             assert_eq!(
                 deserialize(config),
-                Filter {
-                    schema: None,
-                    data: Some(TableList::Except(vec![
-                        String::from("table1"),
-                        String::from("table2")
-                    ])),
-                }
+                Filter::new(
+                    TableList::default(),
+                    TableList::Except(vec![String::from("table1"), String::from("table2")])
+                )
             );
         }
 
@@ -235,13 +263,10 @@ mod tests {
 
             assert_eq!(
                 deserialize(config),
-                Filter {
-                    schema: Some(TableList::Only(vec![
-                        String::from("table1"),
-                        String::from("table2")
-                    ])),
-                    data: None,
-                }
+                Filter::new(
+                    TableList::Only(vec![String::from("table1"), String::from("table2")]),
+                    TableList::default(),
+                )
             );
         }
 
@@ -259,48 +284,169 @@ mod tests {
 
             assert_eq!(
                 deserialize(config),
-                Filter {
-                    schema: Some(TableList::Except(vec![
-                        String::from("table1"),
-                        String::from("table2")
-                    ])),
-                    data: Some(TableList::Only(vec![String::from("table1"),])),
-                }
+                Filter::new(
+                    TableList::Except(vec![String::from("table1"), String::from("table2")]),
+                    TableList::Only(vec![String::from("table1"),]),
+                )
             );
         }
 
-        #[test]
-        fn filter_schema() {
-            let filter = Filter {
-                schema: Some(TableList::Except(vec![String::from("table1")])),
-                data: None,
-            };
-            assert!(!filter.filter_schema(&String::from("table1")));
-            assert!(filter.filter_schema(&String::from("table2")));
-
-            let filter = Filter {
-                schema: None,
-                data: Some(TableList::Except(vec![String::from("table1")])),
-            };
-            assert!(filter.filter_schema(&String::from("table1")));
-            assert!(filter.filter_schema(&String::from("table2")));
+        mod load_tables {
+            use super::*;
+
+            #[test]
+            fn wildcards() {
+                let schema =
+                    TableList::Except(vec![String::from("public.table1"), String::from("other.*")]);
+                let data = TableList::Only(vec![
+                    String::from("public.table2"),
+                    String::from("public.table1?"),
+                    String::from("other.*"),
+                ]);
+                let tables = vec![
+                    String::from("public.table1"),
+                    String::from("public.table2"),
+                    String::from("public.table3"),
+                    String::from("public.table10"),
+                    String::from("public.table11"),
+                    String::from("other.table1"),
+                    String::from("other.table2"),
+                    String::from("another.table1"),
+                ];
+
+                let mut filter = Filter::new(schema.clone(), data.clone());
+                filter.load_tables(tables);
+
+                assert_eq!(
+                    filter,
+                    Filter {
+                        schema,
+                        matched_schema: TableList::Except(vec![
+                            String::from("public.table1"),
+                            String::from("other.table1"),
+                            String::from("other.table2"),
+                        ]),
+                        data,
+                        matched_data: TableList::Only(vec![
+                            String::from("public.table2"),
+                            String::from("public.table10"),
+                            String::from("public.table11"),
+                            String::from("other.table1"),
+                            String::from("other.table2"),
+                        ]),
+                    }
+                )
+            }
+
+            #[test]
+            fn match_all() {
+                let schema = TableList::Only(vec![String::from("*")]);
+                let data = TableList::Except(vec![String::from("*")]);
+                let tables = vec![
+                    String::from("public.table1"),
+                    String::from("public.table2"),
+                    String::from("other.table1"),
+                    String::from("other.table2"),
+                ];
+
+                let mut filter = Filter::new(schema.clone(), data.clone());
+                filter.load_tables(tables);
+
+                assert_eq!(
+                    filter,
+                    Filter {
+                        schema,
+                        matched_schema: TableList::Only(vec![
+                            String::from("public.table1"),
+                            String::from("public.table2"),
+                            String::from("other.table1"),
+                            String::from("other.table2"),
+                        ]),
+                        data,
+                        matched_data: TableList::Except(vec![
+                            String::from("public.table1"),
+                            String::from("public.table2"),
+                            String::from("other.table1"),
+                            String::from("other.table2"),
+                        ]),
+                    }
+                )
+            }
         }
 
-        #[test]
-        fn filter_data() {
-            let filter = Filter {
-                schema: Some(TableList::Except(vec![String::from("table1")])),
-                data: None,
-            };
-            assert!(filter.filter_data(&String::from("table1")));
-            assert!(filter.filter_data(&String::from("table2")));
-
-            let filter = Filter {
-                schema: None,
-                data: Some(TableList::Only(vec![String::from("table1")])),
-            };
-            assert!(filter.filter_data(&String::from("table1")));
-            assert!(!filter.filter_data(&String::from("table2")));
+        mod filter_table {
+            use super::*;
+
+            #[test]
+            fn only_schema() {
+                let mut filter = Filter::new(
+                    TableList::Except(vec![String::from("table1")]),
+                    TableList::default(),
+                );
+                filter.load_tables(vec![String::from("table1"), String::from("table2")]);
+                assert!(!filter.filter_table("table1"));
+                assert!(filter.filter_table("table2"));
+            }
+
+            #[test]
+            fn only_data() {
+                let mut filter = Filter::new(
+                    TableList::default(),
+                    TableList::Only(vec![String::from("table1")]),
+                );
+                filter.load_tables(vec![String::from("table1"), String::from("table2")]);
+                assert!(filter.filter_table("table1"));
+                assert!(!filter.filter_table("table2"));
+            }
+
+            #[test]
+            fn schema_and_data() {
+                let mut filter = Filter::new(
+                    TableList::Except(vec![String::from("table1")]),
+                    TableList::Only(vec![String::from("table1"), String::from("table2")]),
+                );
+                filter.load_tables(vec![String::from("table1"), String::from("table2")]);
+                assert!(!filter.filter_table("table1"));
+                assert!(filter.filter_table("table2"));
+            }
+
+            #[test]
+            fn missed_tables() {
+                let mut filter = Filter::new(
+                    TableList::default(),
+                    TableList::Only(vec![String::from("table1"), String::from("table2")]),
+                );
+                filter.load_tables(vec![String::from("table1")]);
+                assert!(filter.filter_table("table1"));
+                assert!(!filter.filter_table("table2"));
+            }
+
+            #[test]
+            fn wildcards() {
+                let mut filter = Filter::new(
+                    TableList::Except(vec![String::from("table1*")]),
+                    TableList::Only(vec![
+                        String::from("table1"),
+                        String::from("table2?1"),
+                        String::from("table3"),
+                    ]),
+                );
+                filter.load_tables(vec![
+                    String::from("table1"),
+                    String::from("table10"),
+                    String::from("table2"),
+                    String::from("table201"),
+                    String::from("table3"),
+                    String::from("table301"),
+                ]);
+
+                assert!(!filter.filter_table("table1"));
+                assert!(!filter.filter_table("table10"));
+                assert!(!filter.filter_table("table2"));
+                assert!(filter.filter_table("table201"));
+                assert!(filter.filter_table("table3"));
+                assert!(!filter.filter_table("table301"));
+            }
         }
     }
 }
diff --git a/docs/config.md b/docs/config.md
index 4fcdc67b..1073b15c 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -587,7 +587,13 @@ default:
 
 You can specify which tables you choose (whitelisting) or ignore (blacklisting) to dump.
 
-For now, you must use the full table names here (with schema).
+You must use the full table names here (with schema). 
+
+You can use wildcards: 
+
+* `?` matches exactly one occurrence of any character;
+* `*` matches arbitrary many (including zero) occurrences of any character.
+
 
 ### Examples
 
@@ -643,6 +649,22 @@ filter:
       - public.markets
 ```
 
+For skipping schema and data from all tables in the schema `other` (you should use the quotes):
+
+```yaml
+filter:
+  schema:
+    except:
+      - "other.*"
+```
+
+For dumping data only from `public.table1`, `public.table2`, `public.table3`, etc:
+
+```yaml
+filter:
+  - "public.table?"
+```
+
 If you need only a subset of the data, please refer to the [query](#query) section.
 
 ## templates


Test patch: