Repository: datanymizer/datanymizer (language: rust)

Issue:
Error: no matching tables were found
@evgeniy-r I am facing an issue while using the cli with my db. I am not sure that whether I am using the config correctly, can you help me rectify the issue. 

thank you in advance.

### Background
I have a database with multiple schemas and I want to copy over the schema `default$default` and the tables under that schema. Of course, I also anonymize certain fields in the tables.

### config.yml
```
tables:
  - name: default$default.Profile
    rules:
      firstName:
        first_name: {}
        ...
        ...
filter:
  schema:
    only:
      - default$default
```

### Output
```
$ ./bin/pg_datanymizer -f backup.sql -c anonymizer/config.yml --accept_invalid_certs $POSTGRES_CONNECTION_STRING
Prepare data scheme...
pg_dump error. Command:
pg_dump --section pre-data -t default$default <POSTGRES_CONNECTION_STRING>
Output:
pg_dump: error: no matching tables were found
```


Discussion / hints:
I will take a look.
In the `schema`section you should place a list of tables, for which you want to dump their schema (structure), not a schema name.
```yaml
filter:
  schema:
    only:
      - default$default.Profile
      - ...........
```
We will think about using wildcards (`*`).

Also, in the `tables` section please use names without a schema (it is an old issue, we should fix it, but the most of people anonymize data in just one schema, so it hasn't been fixed yet):
```yaml
tables:
  - name: Profile
  - rules:
  .............
```

understood. 
Tried the following and it is giving me the same error.

### config.yml
```
tables:
  # Table name in database
  - name: Profile
    rules:
      firstName:
        first_name: {}
        ...
        ...
filter:
  schema:
    only:
      - default$default.Profile
```
### output
```
$ ./bin/pg_datanymizer -f backup.sql -c anonymizer/config.yml --accept_invalid_certs $POSTGRES_CONNECTION_STRING
Prepare data scheme...
pg_dump error. Command:
pg_dump --section pre-data -t default$default.Profile  <POSTGRES_CONNECTION_STRING>
Output:
pg_dump: error: no matching tables were found
```
You just discovered another bug when working with schemas other than `public`.
Thank you and sorry (

We will fix it in a couple of days.

haha, no problem, rather thank you so much for maintaining the project so actively :) 
Also, I had another suggestion when using the connection string:
`postgresql://...` -  it throws the following error `Error: Scheme url error` 
changing the URL to 
`postgres://...` solves the issue.

Maybe in the future you could add support for both
OK, I will check this issue too.
hey @evgeniy-r , if possible, can I get an estimate on the timeline for this fix? I am planning to implement this in my project and it would help me plan.
Hello!

I plan to fix this issue this week.
That sounds great, thank you.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49f0ffe5..ca9f071b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### ⚙️ Changed
 
 ### 🛠 Fixed
-- Fix demo docker environment [#109](https://github.com/datanymizer/datanymizer/pull/109) ([@mgrachev](https://github.com/mgrachev))
+- Fix problems with different table names [#102](https://github.com/datanymizer/datanymizer/pull/102)
+  ([@evgeniy-r](https://github.com/evgeniy-r))
+- Fix demo docker environment [#109](https://github.com/datanymizer/datanymizer/pull/109)
+  ([@mgrachev](https://github.com/mgrachev)) 
 
 ## [v0.3.1] - 2021-09-20
 ### 🚀 Added
diff --git a/datanymizer_dumper/src/lib.rs b/datanymizer_dumper/src/lib.rs
index 28e6d055..63f4e3d3 100644
--- a/datanymizer_dumper/src/lib.rs
+++ b/datanymizer_dumper/src/lib.rs
@@ -66,7 +66,7 @@ pub trait SchemaInspector: 'static + Sized + Send + Clone {
     fn get_table_size(
         &self,
         connection: &mut <Self::Dumper as Dumper>::Connection,
-        table_name: String,
+        table: &Self::Table,
     ) -> Result<i64>;
 
     /// Get all dependencies (by FK) for `table` in database
diff --git a/datanymizer_dumper/src/postgres/dumper.rs b/datanymizer_dumper/src/postgres/dumper.rs
index 559d0250..14b5cacf 100644
--- a/datanymizer_dumper/src/postgres/dumper.rs
+++ b/datanymizer_dumper/src/postgres/dumper.rs
@@ -52,7 +52,7 @@ impl PgDumper {
     fn run_pg_dump(&mut self, section: &str) -> Result<()> {
         let program = &self.pg_dump_location;
         let args = vec!["--section", section];
-        let table_args = Self::table_args(&self.engine.settings.filter);
+        let table_args = Self::table_args(&self.engine.settings.filter)?;
         let db_url = self.engine.settings.source.get_database_url();
 
         let dump_output = Command::new(program)
@@ -95,23 +95,22 @@ impl PgDumper {
         );
     }
 
-    fn table_args(filter: &Option<Filter>) -> Vec<String> {
+    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",
                 };
-
-                return list
-                    .tables()
-                    .iter()
-                    .flat_map(|table| vec![String::from(flag), table.clone()])
-                    .collect();
+                for table in list.tables() {
+                    args.push(String::from(flag));
+                    args.push(PgTable::quote_table_name(table.as_str())?);
+                }
             }
         }
 
-        Vec::new()
+        Ok(args)
     }
 
     fn dump_table(&mut self, table: &PgTable, qw: &mut QueryWrapper) -> Result<()> {
@@ -124,7 +123,7 @@ impl PgDumper {
         self.dump_writer.write_all(table.query_from().as_bytes())?;
         self.dump_writer.write_all(b"\n")?;
 
-        let cfg = settings.get_table(table.get_name().as_str());
+        let cfg = settings.find_table(&[table.get_full_name().as_str(), table.get_name().as_str()]);
 
         self.init_progress_bar(table.count_of_query_to(cfg), &table.get_full_name());
 
@@ -255,22 +254,22 @@ mod tests {
     #[test]
     fn table_args() {
         let empty: Vec<String> = vec![];
-        assert_eq!(PgDumper::table_args(&None), empty);
+        assert_eq!(PgDumper::table_args(&None).unwrap(), empty);
 
         let filter = Filter {
             schema: Some(TableList::Except(vec![String::from("table1")])),
             data: None,
         };
         assert_eq!(
-            PgDumper::table_args(&Some(filter)),
-            vec![String::from("-T"), String::from("table1")]
+            PgDumper::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")])),
         };
-        assert_eq!(PgDumper::table_args(&Some(filter)), empty);
+        assert_eq!(PgDumper::table_args(&Some(filter)).unwrap(), empty);
 
         let filter = Filter {
             schema: Some(TableList::Only(vec![
@@ -280,12 +279,12 @@ mod tests {
             data: None,
         };
         assert_eq!(
-            PgDumper::table_args(&Some(filter)),
+            PgDumper::table_args(&Some(filter)).unwrap(),
             vec![
                 String::from("-t"),
-                String::from("table1"),
+                String::from("\"table1\""),
                 String::from("-t"),
-                String::from("table2")
+                String::from("\"table2\"")
             ]
         );
     }
diff --git a/datanymizer_dumper/src/postgres/row.rs b/datanymizer_dumper/src/postgres/row.rs
index 8a603f0c..653112f6 100644
--- a/datanymizer_dumper/src/postgres/row.rs
+++ b/datanymizer_dumper/src/postgres/row.rs
@@ -72,7 +72,7 @@ mod tests {
         "#;
         let settings = Settings::from_yaml(config, String::new()).unwrap();
 
-        let mut table = PgTable::new("table_name".to_string(), None);
+        let mut table = PgTable::new("table_name".to_string(), "public".to_string());
 
         let col1 = PgColumn {
             position: 1,
diff --git a/datanymizer_dumper/src/postgres/schema_inspector.rs b/datanymizer_dumper/src/postgres/schema_inspector.rs
index ac09a901..fa3d5f7c 100644
--- a/datanymizer_dumper/src/postgres/schema_inspector.rs
+++ b/datanymizer_dumper/src/postgres/schema_inspector.rs
@@ -27,15 +27,25 @@ const TABLE_FOREIGN_KEYS: &str = "SELECT
                                     JOIN information_schema.constraint_column_usage AS ccu
                                     ON ccu.constraint_name = tc.constraint_name
                                     AND ccu.table_schema = tc.table_schema
-                                WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name=$1";
+                                WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1";
 
 const TABLE_COLUMNS_QUERY: &str = "SELECT cc.column_name, cc.ordinal_position, cc.data_type, pt.oid
                                    FROM information_schema.columns as cc
                                    JOIN pg_catalog.pg_type as pt
                                    ON cc.udt_name = pt.typname
-                                   WHERE cc.table_schema=$1 and cc.table_name = $2
+                                   WHERE cc.table_schema = $1 and cc.table_name = $2
                                    ORDER BY cc.ordinal_position ASC";
 
+const TABLE_SIZE_QUERY: &str =
+    "SELECT
+    (pg_catalog.pg_class.reltuples / COALESCE(NULLIF(pg_catalog.pg_class.relpages, 0), 1))::bigint * (
+        pg_relation_size(pg_catalog.pg_class.oid)::bigint /
+        current_setting('block_size')::bigint
+    )::bigint AS len
+    FROM pg_catalog.pg_class
+    INNER JOIN pg_catalog.pg_namespace ON pg_catalog.pg_class.relnamespace = pg_catalog.pg_namespace.oid
+    WHERE pg_catalog.pg_class.relname = $1 AND pg_catalog.pg_namespace.nspname = $2";
+
 #[derive(Clone)]
 pub struct PgSchemaInspector;
 
@@ -63,7 +73,7 @@ impl SchemaInspector for PgSchemaInspector {
                     table.set_sequences(sequences);
                 };
 
-                match self.get_table_size(connection, table.get_name()) {
+                match self.get_table_size(connection, &table) {
                     Ok(size) => table.size = size as i64,
                     Err(e) => panic!("ERR: {}", e),
                 }
@@ -80,19 +90,9 @@ impl SchemaInspector for PgSchemaInspector {
     fn get_table_size(
         &self,
         connection: &mut <Self::Dumper as Dumper>::Connection,
-        table_name: String,
+        table: &Self::Table,
     ) -> Result<i64> {
-        let query: &str = &format!(
-            "SELECT
-            (reltuples/COALESCE(NULLIF(relpages, 0), 1))::bigint * (
-                pg_relation_size('{table_name}')::bigint /
-                (current_setting('block_size')::bigint)
-            )::bigint as len
-            FROM pg_class where relname = '{table_name}'",
-            table_name = table_name
-        );
-
-        let row = connection.query_one(query, &[])?;
+        let row = connection.query_one(TABLE_SIZE_QUERY, &[&table.tablename, &table.schemaname])?;
         let size: i64 = row.get("len");
         Ok(size)
     }
@@ -111,7 +111,7 @@ impl SchemaInspector for PgSchemaInspector {
         let tables: Vec<Self::Table> = fkeys_iterator
             // Table from foreign key
             .map(|fkey: ForeignKey| {
-                PgTable::new(fkey.foreign_table_name, Some(fkey.foreign_table_schema))
+                PgTable::new(fkey.foreign_table_name, fkey.foreign_table_schema)
             })
             // Columns for table
             .map(|mut table| {
@@ -122,7 +122,7 @@ impl SchemaInspector for PgSchemaInspector {
                     table.set_sequences(sequences);
                 };
 
-                match self.get_table_size(connection, table.get_name()) {
+                match self.get_table_size(connection, &table) {
                     Ok(size) => table.size = size as i64,
                     Err(e) => println!("ERR: {}", e),
                 }
@@ -158,8 +158,8 @@ impl PgSchemaInspector {
         for col in table.columns.iter() {
             let full_name: Option<String> = connection
                 .query_one(
-                    "SELECT pg_get_serial_sequence($1, $2)",
-                    &[&table.get_full_name(), &col.name],
+                    "SELECT pg_catalog.pg_get_serial_sequence($1, $2)",
+                    &[&table.quoted_full_name(), &col.name],
                 )?
                 .get(0);
             if let Some(full_name) = full_name {
diff --git a/datanymizer_dumper/src/postgres/table.rs b/datanymizer_dumper/src/postgres/table.rs
index 1a71df62..c405e7a8 100644
--- a/datanymizer_dumper/src/postgres/table.rs
+++ b/datanymizer_dumper/src/postgres/table.rs
@@ -1,5 +1,6 @@
 use super::{column::PgColumn, dumper::PgDumper, row::PgRow, sequence::PgSequence};
 use crate::Table;
+use anyhow::{anyhow, Result};
 use datanymizer_engine::{Query as QueryCfg, Table as TableCfg};
 use postgres::{types::Type, Row as PostgresRow};
 use std::{
@@ -10,7 +11,7 @@ use std::{
 #[derive(Debug, Clone, Eq)]
 pub struct PgTable {
     pub tablename: String,
-    pub schemaname: Option<String>,
+    pub schemaname: String,
     pub columns: Vec<PgColumn>,
     pub sequences: Vec<PgSequence>,
     column_indexes: HashMap<String, usize>,
@@ -42,13 +43,7 @@ impl Table<Type> for PgTable {
 
     // Returns table name with the schema or other prefix, based on database type
     fn get_full_name(&self) -> String {
-        let mut full_name = String::from("");
-        if let Some(schema) = self.schemaname.clone() {
-            full_name.push_str(&schema);
-            full_name.push('.');
-        }
-        full_name.push_str(&self.tablename);
-        full_name
+        format!("{}.{}", self.schemaname, self.tablename)
     }
 
     fn get_columns(&self) -> Vec<Self::Column> {
@@ -69,7 +64,7 @@ impl Table<Type> for PgTable {
 }
 
 impl PgTable {
-    pub fn new(tablename: String, schemaname: Option<String>) -> Self {
+    pub fn new(tablename: String, schemaname: String) -> Self {
         Self {
             tablename,
             schemaname,
@@ -80,6 +75,19 @@ impl PgTable {
         }
     }
 
+    pub fn quote_table_name(name: &str) -> Result<String> {
+        let parts: Vec<_> = name.split('.').collect();
+        match parts.len() {
+            1 => Ok(format!(r#""{}""#, name)),
+            2 => Ok(format!(r#""{}"."{}""#, parts[0], parts[1])),
+            _ => Err(anyhow!("Invalid table name {}", name)),
+        }
+    }
+
+    pub fn quoted_full_name(&self) -> String {
+        format!(r#""{}"."{}""#, self.schemaname, self.tablename)
+    }
+
     pub fn set_columns(&mut self, columns: Vec<PgColumn>) {
         let mut map: HashMap<String, usize> = HashMap::with_capacity(columns.len());
         let mut column_refs: Vec<_> = columns.iter().collect();
@@ -142,7 +150,7 @@ impl PgTable {
     pub fn query_from(&self) -> String {
         format!(
             "COPY {}({}) FROM STDIN;",
-            self.get_full_name(),
+            self.quoted_full_name(),
             self.quoted_columns().join(", "),
         )
     }
@@ -171,7 +179,7 @@ impl PgTable {
     fn default_query(&self) -> String {
         format!(
             "COPY {}({}) TO STDOUT",
-            self.get_full_name(),
+            self.quoted_full_name(),
             self.quoted_columns().join(", ")
         )
     }
@@ -179,7 +187,7 @@ impl PgTable {
     fn query_with_select(&self, cs: Vec<Option<String>>, limit: Option<u64>) -> String {
         format!(
             "COPY (SELECT * FROM {}{}{}) TO STDOUT",
-            self.get_full_name(),
+            self.quoted_full_name(),
             Self::sql_conditions(cs),
             Self::sql_limit(limit),
         )
@@ -208,7 +216,7 @@ impl PgTable {
 
 impl From<PostgresRow> for PgTable {
     fn from(row: PostgresRow) -> Self {
-        Self::new(row.get("tablename"), row.try_get("schemaname").ok())
+        Self::new(row.get("tablename"), row.get("schemaname"))
     }
 }
 
@@ -219,21 +227,32 @@ mod tests {
 
     #[test]
     fn table_full_name() {
-        let table = PgTable::new(String::from("name"), Some(String::from("public")));
+        let table = PgTable::new(String::from("name"), String::from("public"));
         assert_eq!(table.get_name(), String::from("name"));
         assert_eq!(table.get_full_name(), String::from("public.name"));
     }
 
     #[test]
-    fn table_without_scheme() {
-        let table = PgTable::new(String::from("name"), None);
-        assert_eq!(table.get_name(), String::from("name"));
-        assert_eq!(table.get_full_name(), String::from("name"));
+    fn quote_table_name() {
+        let name = PgTable::quote_table_name("table").unwrap();
+        assert_eq!(name, "\"table\"");
+
+        let name = PgTable::quote_table_name("public.table").unwrap();
+        assert_eq!(name, "\"public\".\"table\"");
+
+        let name = PgTable::quote_table_name("public.name.");
+        assert!(name.is_err());
+    }
+
+    #[test]
+    fn quoted_full_name() {
+        let table = PgTable::new(String::from("name"), String::from("public2"));
+        assert_eq!(table.quoted_full_name(), r#""public2"."name""#)
     }
 
     #[test]
     fn set_columns() {
-        let mut table = PgTable::new(String::from("name"), None);
+        let mut table = PgTable::new(String::from("name"), String::from("public"));
 
         let col1 = PgColumn {
             position: 1,
@@ -290,7 +309,7 @@ mod tests {
         }
 
         fn table() -> PgTable {
-            let mut table = PgTable::new(table_name(), None);
+            let mut table = PgTable::new(table_name(), String::from("public"));
             table.set_columns(columns());
             table.size = 1000;
             table
@@ -310,7 +329,7 @@ mod tests {
             assert_eq!(table().transformed_query_to(None, 0), None);
             assert_eq!(
                 table().untransformed_query_to(None, 0).unwrap(),
-                "COPY some_table(\"col1\", \"col2\") TO STDOUT"
+                "COPY \"public\".\"some_table\"(\"col1\", \"col2\") TO STDOUT"
             );
             assert_eq!(table().count_of_query_to(None), 1000);
         }
@@ -321,7 +340,7 @@ mod tests {
 
             assert_eq!(
                 table().transformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY some_table(\"col1\", \"col2\") TO STDOUT"
+                "COPY \"public\".\"some_table\"(\"col1\", \"col2\") TO STDOUT"
             );
             assert_eq!(table().untransformed_query_to(Some(&cfg), 0), None);
             assert_eq!(table().count_of_query_to(Some(&cfg)), 1000);
@@ -337,7 +356,7 @@ mod tests {
 
             assert_eq!(
                 table().transformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table LIMIT 100) TO STDOUT"
+                "COPY (SELECT * FROM \"public\".\"some_table\" LIMIT 100) TO STDOUT"
             );
             assert_eq!(table().untransformed_query_to(Some(&cfg), 0), None);
             assert_eq!(table().count_of_query_to(Some(&cfg)), 100);
@@ -353,7 +372,7 @@ mod tests {
 
             assert_eq!(
                 table().transformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table WHERE (col1 = 'value')) TO STDOUT"
+                "COPY (SELECT * FROM \"public\".\"some_table\" WHERE (col1 = 'value')) TO STDOUT"
             );
             assert_eq!(table().untransformed_query_to(Some(&cfg), 0), None);
             assert_eq!(table().count_of_query_to(Some(&cfg)), 1000);
@@ -369,11 +388,11 @@ mod tests {
 
             assert_eq!(
                 table().transformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table WHERE (col1 = 'value')) TO STDOUT"
+                "COPY (SELECT * FROM \"public\".\"some_table\" WHERE (col1 = 'value')) TO STDOUT"
             );
             assert_eq!(
                 table().untransformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table WHERE NOT (col1 = 'value')) TO STDOUT"
+                "COPY (SELECT * FROM \"public\".\"some_table\" WHERE NOT (col1 = 'value')) TO STDOUT"
             );
             assert_eq!(table().count_of_query_to(Some(&cfg)), 1000);
         }
@@ -388,12 +407,12 @@ mod tests {
 
             assert_eq!(
                 table().transformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table \
+                "COPY (SELECT * FROM \"public\".\"some_table\" \
                 WHERE (col1 = 'value') AND (col2 <> 'other_value') LIMIT 500) TO STDOUT"
             );
             assert_eq!(
                 table().untransformed_query_to(Some(&cfg), 0).unwrap(),
-                "COPY (SELECT * FROM some_table \
+                "COPY (SELECT * FROM \"public\".\"some_table\" \
                 WHERE (col1 = 'value') AND NOT (col2 <> 'other_value') LIMIT 500) TO STDOUT"
             );
             assert_eq!(table().count_of_query_to(Some(&cfg)), 500);
@@ -412,11 +431,11 @@ mod tests {
 
                 assert_eq!(
                     table().transformed_query_to(Some(&cfg), 100).unwrap(),
-                    "COPY (SELECT * FROM some_table WHERE (col1 = 'value')) TO STDOUT"
+                    "COPY (SELECT * FROM \"public\".\"some_table\" WHERE (col1 = 'value')) TO STDOUT"
                 );
                 assert_eq!(
                     table().untransformed_query_to(Some(&cfg), 100).unwrap(),
-                    "COPY (SELECT * FROM some_table WHERE NOT (col1 = 'value')) TO STDOUT"
+                    "COPY (SELECT * FROM \"public\".\"some_table\" WHERE NOT (col1 = 'value')) TO STDOUT"
                 );
             }
 
@@ -430,11 +449,11 @@ mod tests {
 
                 assert_eq!(
                     table().transformed_query_to(Some(&cfg), 100).unwrap(),
-                    "COPY (SELECT * FROM some_table WHERE (col1 = 'value') LIMIT 50) TO STDOUT"
+                    "COPY (SELECT * FROM \"public\".\"some_table\" WHERE (col1 = 'value') LIMIT 50) TO STDOUT"
                 );
                 assert_eq!(
                     table().untransformed_query_to(Some(&cfg), 100).unwrap(),
-                    "COPY (SELECT * FROM some_table WHERE NOT (col1 = 'value') LIMIT 50) TO STDOUT"
+                    "COPY (SELECT * FROM \"public\".\"some_table\" WHERE NOT (col1 = 'value') LIMIT 50) TO STDOUT"
                 );
             }
 
diff --git a/datanymizer_engine/src/settings/mod.rs b/datanymizer_engine/src/settings/mod.rs
index 01d06f9a..61b36d63 100644
--- a/datanymizer_engine/src/settings/mod.rs
+++ b/datanymizer_engine/src/settings/mod.rs
@@ -92,6 +92,16 @@ impl Settings {
         self.tables.iter().find(|t| t.name == name)
     }
 
+    pub fn find_table(&self, names: &[&str]) -> Option<&Table> {
+        for name in names {
+            let table = self.get_table(name);
+            if table.is_some() {
+                return table;
+            }
+        }
+        None
+    }
+
     pub fn destination(&self) -> Result<String> {
         self.destination
             .clone()
@@ -157,6 +167,38 @@ mod tests {
         );
     }
 
+    #[test]
+    fn find_table() {
+        let config = r#"
+            tables:
+              - name: companies
+                rules:
+                  name:
+                    company_name: {}
+              - name: users
+                rules:
+                  name:
+                    person_name: {}
+              - name: other_schema.users
+                rules:
+                  other_name:
+                    person_name: {}
+            "#;
+        let s = Settings::from_yaml(config, String::new()).unwrap();
+
+        let t = s.find_table(&["some_table"]);
+        assert!(t.is_none());
+
+        let t = s.find_table(&["some_table", "users"]);
+        assert_eq!(t.unwrap().name, "users");
+
+        let t = s.find_table(&["users", "other_schema.users"]);
+        assert_eq!(t.unwrap().name, "users");
+
+        let t = s.find_table(&["other_schema.users", "users"]);
+        assert_eq!(t.unwrap().name, "other_schema.users");
+    }
+
     mod transformers_for {
         use super::*;
 
diff --git a/docs/config.md b/docs/config.md
index a1025874..6564bd77 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -230,6 +230,9 @@ tables:
 | [rule_order](#rule_order) | no        | list       | An order of rule execution
 | [query](#query)           | no        | dictionary | Conditions for SQL queries for dumping data 
 
+You can use table names with schema (e.g. `public.users`) or without it (just `users`). In the latter case, this means
+that the rules will be applied to the `users` table in any schema.
+
 #### rules
 
 Anonymization rules (we call them `transformers`) for the table columns.
@@ -552,7 +555,11 @@ default:
 
 You can specify which tables you choose (whitelisting) or ignore (blacklisting) to dump.
 
-For dumping only `public.markets` and `public.users` data.
+For now, you must use the full table names here (with schema).
+
+### Examples
+
+For dumping only `public.markets` and `public.users` data:
 
 ```yaml
 filter:
@@ -561,7 +568,7 @@ filter:
     - public.users
 ```
 
-For ignoring these tables and dump data from others.
+For ignoring these tables and dump data from others:
 
 ```yaml
 filter:
@@ -572,7 +579,7 @@ filter:
 
 You can also specify data and schema filters separately.
 
-This is equivalent to the previous example.
+This is equivalent to the previous example:
 
 ```yaml
 filter:
@@ -582,7 +589,7 @@ filter:
       - public.users
 ```
 
-For skipping schema and data from other tables.
+For skipping schema and data from other tables:
 
 ```yaml
 filter:
@@ -592,7 +599,7 @@ filter:
       - public.users
 ```
 
-For skipping schema for `markets` table and dumping data only from `users` table.
+For skipping schema for `markets` table and dumping data only from `users` table:
 
 ```yaml
 filter:


Test patch: