| 1 | #include "duckdb/catalog/catalog_search_path.hpp" |
| 2 | |
| 3 | #include "duckdb/common/constants.hpp" |
| 4 | #include "duckdb/common/exception.hpp" |
| 5 | #include "duckdb/common/string_util.hpp" |
| 6 | #include "duckdb/main/client_context.hpp" |
| 7 | #include "duckdb/catalog/catalog.hpp" |
| 8 | #include "duckdb/main/database_manager.hpp" |
| 9 | |
| 10 | namespace duckdb { |
| 11 | |
| 12 | CatalogSearchEntry::CatalogSearchEntry(string catalog_p, string schema_p) |
| 13 | : catalog(std::move(catalog_p)), schema(std::move(schema_p)) { |
| 14 | } |
| 15 | |
| 16 | string CatalogSearchEntry::ToString() const { |
| 17 | if (catalog.empty()) { |
| 18 | return WriteOptionallyQuoted(input: schema); |
| 19 | } else { |
| 20 | return WriteOptionallyQuoted(input: catalog) + "." + WriteOptionallyQuoted(input: schema); |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | string CatalogSearchEntry::WriteOptionallyQuoted(const string &input) { |
| 25 | for (idx_t i = 0; i < input.size(); i++) { |
| 26 | if (input[i] == '.' || input[i] == ',') { |
| 27 | return "\"" + input + "\"" ; |
| 28 | } |
| 29 | } |
| 30 | return input; |
| 31 | } |
| 32 | |
| 33 | string CatalogSearchEntry::ListToString(const vector<CatalogSearchEntry> &input) { |
| 34 | string result; |
| 35 | for (auto &entry : input) { |
| 36 | if (!result.empty()) { |
| 37 | result += "," ; |
| 38 | } |
| 39 | result += entry.ToString(); |
| 40 | } |
| 41 | return result; |
| 42 | } |
| 43 | |
| 44 | CatalogSearchEntry CatalogSearchEntry::ParseInternal(const string &input, idx_t &idx) { |
| 45 | string catalog; |
| 46 | string schema; |
| 47 | string entry; |
| 48 | bool finished = false; |
| 49 | normal: |
| 50 | for (; idx < input.size(); idx++) { |
| 51 | if (input[idx] == '"') { |
| 52 | idx++; |
| 53 | goto quoted; |
| 54 | } else if (input[idx] == '.') { |
| 55 | goto separator; |
| 56 | } else if (input[idx] == ',') { |
| 57 | finished = true; |
| 58 | goto separator; |
| 59 | } |
| 60 | entry += input[idx]; |
| 61 | } |
| 62 | finished = true; |
| 63 | goto separator; |
| 64 | quoted: |
| 65 | //! look for another quote |
| 66 | for (; idx < input.size(); idx++) { |
| 67 | if (input[idx] == '"') { |
| 68 | //! unquote |
| 69 | idx++; |
| 70 | goto normal; |
| 71 | } |
| 72 | entry += input[idx]; |
| 73 | } |
| 74 | throw ParserException("Unterminated quote in qualified name!" ); |
| 75 | separator: |
| 76 | if (entry.empty()) { |
| 77 | throw ParserException("Unexpected dot - empty CatalogSearchEntry" ); |
| 78 | } |
| 79 | if (schema.empty()) { |
| 80 | // if we parse one entry it is the schema |
| 81 | schema = std::move(entry); |
| 82 | } else if (catalog.empty()) { |
| 83 | // if we parse two entries it is [catalog.schema] |
| 84 | catalog = std::move(schema); |
| 85 | schema = std::move(entry); |
| 86 | } else { |
| 87 | throw ParserException("Too many dots - expected [schema] or [catalog.schema] for CatalogSearchEntry" ); |
| 88 | } |
| 89 | entry = "" ; |
| 90 | idx++; |
| 91 | if (finished) { |
| 92 | goto final; |
| 93 | } |
| 94 | goto normal; |
| 95 | final: |
| 96 | if (schema.empty()) { |
| 97 | throw ParserException("Unexpected end of entry - empty CatalogSearchEntry" ); |
| 98 | } |
| 99 | return CatalogSearchEntry(std::move(catalog), std::move(schema)); |
| 100 | } |
| 101 | |
| 102 | CatalogSearchEntry CatalogSearchEntry::Parse(const string &input) { |
| 103 | idx_t pos = 0; |
| 104 | auto result = ParseInternal(input, idx&: pos); |
| 105 | if (pos < input.size()) { |
| 106 | throw ParserException("Failed to convert entry \"%s\" to CatalogSearchEntry - expected a single entry" , input); |
| 107 | } |
| 108 | return result; |
| 109 | } |
| 110 | |
| 111 | vector<CatalogSearchEntry> CatalogSearchEntry::ParseList(const string &input) { |
| 112 | idx_t pos = 0; |
| 113 | vector<CatalogSearchEntry> result; |
| 114 | while (pos < input.size()) { |
| 115 | auto entry = ParseInternal(input, idx&: pos); |
| 116 | result.push_back(x: entry); |
| 117 | } |
| 118 | return result; |
| 119 | } |
| 120 | |
| 121 | CatalogSearchPath::CatalogSearchPath(ClientContext &context_p) : context(context_p) { |
| 122 | Reset(); |
| 123 | } |
| 124 | |
| 125 | void CatalogSearchPath::Reset() { |
| 126 | vector<CatalogSearchEntry> empty; |
| 127 | SetPaths(empty); |
| 128 | } |
| 129 | |
| 130 | string CatalogSearchPath::GetSetName(CatalogSetPathType set_type) { |
| 131 | switch (set_type) { |
| 132 | case CatalogSetPathType::SET_SCHEMA: |
| 133 | return "SET schema" ; |
| 134 | case CatalogSetPathType::SET_SCHEMAS: |
| 135 | return "SET search_path" ; |
| 136 | default: |
| 137 | throw InternalException("Unrecognized CatalogSetPathType" ); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | void CatalogSearchPath::Set(vector<CatalogSearchEntry> new_paths, CatalogSetPathType set_type) { |
| 142 | if (set_type != CatalogSetPathType::SET_SCHEMAS && new_paths.size() != 1) { |
| 143 | throw CatalogException("%s can set only 1 schema. This has %d" , GetSetName(set_type), new_paths.size()); |
| 144 | } |
| 145 | for (auto &path : new_paths) { |
| 146 | auto schema_entry = Catalog::GetSchema(context, catalog_name: path.catalog, schema_name: path.schema, if_not_found: OnEntryNotFound::RETURN_NULL); |
| 147 | if (schema_entry) { |
| 148 | // we are setting a schema - update the catalog and schema |
| 149 | if (path.catalog.empty()) { |
| 150 | path.catalog = GetDefault().catalog; |
| 151 | } |
| 152 | continue; |
| 153 | } |
| 154 | // only schema supplied - check if this is a catalog instead |
| 155 | if (path.catalog.empty()) { |
| 156 | auto catalog = Catalog::GetCatalogEntry(context, catalog_name: path.schema); |
| 157 | if (catalog) { |
| 158 | auto schema = catalog->GetSchema(context, DEFAULT_SCHEMA, if_not_found: OnEntryNotFound::RETURN_NULL); |
| 159 | if (schema) { |
| 160 | path.catalog = std::move(path.schema); |
| 161 | path.schema = schema->name; |
| 162 | continue; |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | throw CatalogException("%s: No catalog + schema named \"%s\" found." , GetSetName(set_type), path.ToString()); |
| 167 | } |
| 168 | if (set_type == CatalogSetPathType::SET_SCHEMA) { |
| 169 | if (new_paths[0].catalog == TEMP_CATALOG || new_paths[0].catalog == SYSTEM_CATALOG) { |
| 170 | throw CatalogException("%s cannot be set to internal schema \"%s\"" , GetSetName(set_type), |
| 171 | new_paths[0].catalog); |
| 172 | } |
| 173 | } |
| 174 | this->set_paths = std::move(new_paths); |
| 175 | SetPaths(set_paths); |
| 176 | } |
| 177 | |
| 178 | void CatalogSearchPath::Set(CatalogSearchEntry new_value, CatalogSetPathType set_type) { |
| 179 | vector<CatalogSearchEntry> new_paths {std::move(new_value)}; |
| 180 | Set(new_paths: std::move(new_paths), set_type); |
| 181 | } |
| 182 | |
| 183 | const vector<CatalogSearchEntry> &CatalogSearchPath::Get() { |
| 184 | return paths; |
| 185 | } |
| 186 | |
| 187 | string CatalogSearchPath::GetDefaultSchema(const string &catalog) { |
| 188 | for (auto &path : paths) { |
| 189 | if (path.catalog == TEMP_CATALOG) { |
| 190 | continue; |
| 191 | } |
| 192 | if (StringUtil::CIEquals(l1: path.catalog, l2: catalog)) { |
| 193 | return path.schema; |
| 194 | } |
| 195 | } |
| 196 | return DEFAULT_SCHEMA; |
| 197 | } |
| 198 | |
| 199 | string CatalogSearchPath::GetDefaultCatalog(const string &schema) { |
| 200 | for (auto &path : paths) { |
| 201 | if (path.catalog == TEMP_CATALOG) { |
| 202 | continue; |
| 203 | } |
| 204 | if (StringUtil::CIEquals(l1: path.schema, l2: schema)) { |
| 205 | return path.catalog; |
| 206 | } |
| 207 | } |
| 208 | return INVALID_CATALOG; |
| 209 | } |
| 210 | |
| 211 | vector<string> CatalogSearchPath::GetCatalogsForSchema(const string &schema) { |
| 212 | vector<string> schemas; |
| 213 | for (auto &path : paths) { |
| 214 | if (StringUtil::CIEquals(l1: path.schema, l2: schema)) { |
| 215 | schemas.push_back(x: path.catalog); |
| 216 | } |
| 217 | } |
| 218 | return schemas; |
| 219 | } |
| 220 | |
| 221 | vector<string> CatalogSearchPath::GetSchemasForCatalog(const string &catalog) { |
| 222 | vector<string> schemas; |
| 223 | for (auto &path : paths) { |
| 224 | if (StringUtil::CIEquals(l1: path.catalog, l2: catalog)) { |
| 225 | schemas.push_back(x: path.schema); |
| 226 | } |
| 227 | } |
| 228 | return schemas; |
| 229 | } |
| 230 | |
| 231 | const CatalogSearchEntry &CatalogSearchPath::GetDefault() { |
| 232 | const auto &paths = Get(); |
| 233 | D_ASSERT(paths.size() >= 2); |
| 234 | return paths[1]; |
| 235 | } |
| 236 | |
| 237 | void CatalogSearchPath::SetPaths(vector<CatalogSearchEntry> new_paths) { |
| 238 | paths.clear(); |
| 239 | paths.reserve(n: new_paths.size() + 3); |
| 240 | paths.emplace_back(TEMP_CATALOG, DEFAULT_SCHEMA); |
| 241 | for (auto &path : new_paths) { |
| 242 | paths.push_back(x: std::move(path)); |
| 243 | } |
| 244 | paths.emplace_back(INVALID_CATALOG, DEFAULT_SCHEMA); |
| 245 | paths.emplace_back(SYSTEM_CATALOG, DEFAULT_SCHEMA); |
| 246 | paths.emplace_back(SYSTEM_CATALOG, args: "pg_catalog" ); |
| 247 | } |
| 248 | |
| 249 | bool CatalogSearchPath::SchemaInSearchPath(ClientContext &context, const string &catalog_name, |
| 250 | const string &schema_name) { |
| 251 | for (auto &path : paths) { |
| 252 | if (path.schema != schema_name) { |
| 253 | continue; |
| 254 | } |
| 255 | if (path.catalog == catalog_name) { |
| 256 | return true; |
| 257 | } |
| 258 | if (IsInvalidCatalog(str: path.catalog) && catalog_name == DatabaseManager::GetDefaultDatabase(context)) { |
| 259 | return true; |
| 260 | } |
| 261 | } |
| 262 | return false; |
| 263 | } |
| 264 | |
| 265 | } // namespace duckdb |
| 266 | |