Repository: tobymao/sqlglot

Issue:
DuckDB bracket operator is 1-indexed
**Before you file an issue**
- Make sure you specify the "read" dialect eg. parse_one(sql, read="spark") Bigquery, for example
- Check if the issue still exists on main yes

**Fully reproducible code snippet**
Please include a fully reproducible code snippet or the input sql, dialect, and expected output.

DuckDB arrays are 1-indexed. When providing a literal to the subscript/bracket operator the expected output for a transpilation of
```sql
SELECT some_arr[0] AS first FROM blah
```
should render to
```sql
SELECT some_arr[1] AS first FROM blah
```
in DuckDB. However, this is not the case:
```python
>>> transpile("SELECT some_arr[0] AS first FROM blah", read="bigquery", write="duckdb")
['SELECT some_arr[0] AS first FROM blah']
```

I attempted to patch this by incrementing in the case that `exp.Bracket.expressions` is of length 1 and type `Literal`, but this does not seem to be directionally correct (do you still increment in the case where the subscript is a column?). I think there are some semantics concerns here which I'm not well-versed enough to address.

**Official Documentation**
Please include links to official SQL documentation related to your issue.

[DuckDB array documentation](https://duckdb.org/docs/sql/data_types/array.html)



Discussion:
Specifically in the case of [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/operators) (unsure if this is true for other dialects) the user has to specify whether the bracketed expression is an `ORDINAL` (1-indexed), an `OFFSET` (0-indexed), a `SAFE_OFFSET` (0-indexed returns null if OOB), or a `SAFE_ORDINAL` (you get the idea). This also does not seem to be supported today:

```python
>>> transpile("SELECT some_numbers[ORDINAL(1)] AS ordinal_1", read='bigquery', write='duckdb')
['SELECT some_numbers[ORDINAL(1)] AS ordinal_1']
>>> transpile("SELECT some_numbers[SAFE_ORDINAL(1)] AS ordinal_1", read='bigquery', write='duckdb')
['SELECT some_numbers[SAFE_ORDINAL(1)] AS ordinal_1']
```
Interesting, I'd expect this issue to have surfaced earlier but it seems like you're right:

```
D select ([1,2,3])[0];
┌─────────────────────────────┐
│ main.list_value(1, 2, 3)[0] │
│            int32            │
├─────────────────────────────┤
│                             │
└─────────────────────────────┘
D select ([1,2,3])[1];
┌─────────────────────────────┐
│ main.list_value(1, 2, 3)[1] │
│            int32            │
├─────────────────────────────┤
│                           1 │
└─────────────────────────────┘
```
I'll take a look @j1ah0ng, thanks for the report!

Resolving patch:
diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py
--- a/sqlglot/dialects/duckdb.py
+++ b/sqlglot/dialects/duckdb.py
@@ -127,6 +127,7 @@ class DuckDB(Dialect):
     NULL_ORDERING = "nulls_are_last"
     SUPPORTS_USER_DEFINED_TYPES = False
     SAFE_DIVISION = True
+    INDEX_OFFSET = 1
 
     # https://duckdb.org/docs/sql/introduction.html#creating-a-new-table
     RESOLVES_IDENTIFIERS_AS_UPPERCASE = None


Test patch:
diff --git a/tests/dialects/test_duckdb.py b/tests/dialects/test_duckdb.py
--- a/tests/dialects/test_duckdb.py
+++ b/tests/dialects/test_duckdb.py
@@ -1,4 +1,5 @@
 from sqlglot import ErrorLevel, UnsupportedError, exp, parse_one, transpile
+from sqlglot.helper import logger as helper_logger
 from tests.dialects.test_dialect import Validator
 
 
@@ -96,7 +97,6 @@ def test_duckdb(self):
         )
 
         self.validate_identity("SELECT i FROM RANGE(5) AS _(i) ORDER BY i ASC")
-        self.validate_identity("[x.STRING_SPLIT(' ')[1] FOR x IN ['1', '2', 3] IF x.CONTAINS('1')]")
         self.validate_identity("INSERT INTO x BY NAME SELECT 1 AS y")
         self.validate_identity("SELECT 1 AS x UNION ALL BY NAME SELECT 2 AS x")
         self.validate_identity("SELECT SUM(x) FILTER (x = 1)", "SELECT SUM(x) FILTER(WHERE x = 1)")
@@ -504,6 +504,35 @@ def test_duckdb(self):
 
         self.validate_identity("SELECT ISNAN(x)")
 
+    def test_array_index(self):
+        with self.assertLogs(helper_logger) as cm:
+            self.validate_all(
+                "SELECT some_arr[1] AS first FROM blah",
+                read={
+                    "bigquery": "SELECT some_arr[0] AS first FROM blah",
+                },
+                write={
+                    "bigquery": "SELECT some_arr[0] AS first FROM blah",
+                    "duckdb": "SELECT some_arr[1] AS first FROM blah",
+                    "presto": "SELECT some_arr[1] AS first FROM blah",
+                },
+            )
+            self.validate_identity(
+                "[x.STRING_SPLIT(' ')[1] FOR x IN ['1', '2', 3] IF x.CONTAINS('1')]"
+            )
+
+            self.assertEqual(
+                cm.output,
+                [
+                    "WARNING:sqlglot:Applying array index offset (-1)",
+                    "WARNING:sqlglot:Applying array index offset (1)",
+                    "WARNING:sqlglot:Applying array index offset (1)",
+                    "WARNING:sqlglot:Applying array index offset (1)",
+                    "WARNING:sqlglot:Applying array index offset (-1)",
+                    "WARNING:sqlglot:Applying array index offset (1)",
+                ],
+            )
+
     def test_time(self):
         self.validate_identity("SELECT CURRENT_DATE")
         self.validate_identity("SELECT CURRENT_TIMESTAMP")