|
10 | 10 | class ODataClient: |
11 | 11 | """Dataverse Web API client: CRUD, SQL-over-API, and table metadata helpers.""" |
12 | 12 |
|
| 13 | + @staticmethod |
| 14 | + def _escape_odata_quotes(value: str) -> str: |
| 15 | + """Escape single quotes for OData queries (by doubling them).""" |
| 16 | + return value.replace("'", "''") |
| 17 | + |
13 | 18 | def __init__(self, auth, base_url: str, config=None) -> None: |
14 | 19 | self.auth = auth |
15 | 20 | self.base_url = (base_url or "").rstrip("/") |
@@ -97,9 +102,11 @@ def _logical_from_entity_set(self, entity_set: str) -> str: |
97 | 102 | if cached: |
98 | 103 | return cached |
99 | 104 | url = f"{self.api}/EntityDefinitions" |
| 105 | + # Escape single quotes in entity set name |
| 106 | + es_escaped = self._escape_odata_quotes(es) |
100 | 107 | params = { |
101 | 108 | "$select": "LogicalName,EntitySetName", |
102 | | - "$filter": f"EntitySetName eq '{es}'", |
| 109 | + "$filter": f"EntitySetName eq '{es_escaped}'", |
103 | 110 | } |
104 | 111 | r = self._request("get", url, headers=self._headers(), params=params) |
105 | 112 | r.raise_for_status() |
@@ -167,6 +174,13 @@ def _format_key(self, key: str) -> str: |
167 | 174 | k = key.strip() |
168 | 175 | if k.startswith("(") and k.endswith(")"): |
169 | 176 | return k |
| 177 | + # Escape single quotes in alternate key values |
| 178 | + if "=" in k and "'" in k: |
| 179 | + def esc(match): |
| 180 | + # match.group(1) is the key, match.group(2) is the value |
| 181 | + return f"{match.group(1)}='{self._escape_odata_quotes(match.group(2))}'" |
| 182 | + k = re.sub(r"(\w+)=\'([^\']*)\'", esc, k) |
| 183 | + return f"({k})" |
170 | 184 | if len(k) == 36 and "-" in k: |
171 | 185 | return f"({k})" |
172 | 186 | return f"({k})" |
@@ -360,9 +374,11 @@ def _to_pascal(self, name: str) -> str: |
360 | 374 |
|
361 | 375 | def _get_entity_by_schema(self, schema_name: str) -> Optional[Dict[str, Any]]: |
362 | 376 | url = f"{self.api}/EntityDefinitions" |
| 377 | + # Escape single quotes in schema name |
| 378 | + schema_escaped = self._escape_odata_quotes(schema_name) |
363 | 379 | params = { |
364 | 380 | "$select": "MetadataId,LogicalName,SchemaName,EntitySetName", |
365 | | - "$filter": f"SchemaName eq '{schema_name}'", |
| 381 | + "$filter": f"SchemaName eq '{schema_escaped}'", |
366 | 382 | } |
367 | 383 | r = self._request("get", url, headers=self._headers(), params=params) |
368 | 384 | r.raise_for_status() |
|
0 commit comments