Repository: tobymao/sqlglot

Issue:
bigquery user-defined function `OPTIONS` syntax doesn't parse
**Fully reproducible code snippet**

This BigQuery SQL fails to parse:

```
In [6]: import sqlglot as sg

In [7]: sql = '''
   ...: CREATE TEMPORARY FUNCTION string_length_0(strings ARRAY<STRING>)
   ...: RETURNS FLOAT64
   ...: LANGUAGE js AS """
   ...: 'use strict';
   ...: function string_length(strings) {
   ...:     return _.sum(_.map(strings, ((x) => x.length)));
   ...: }
   ...: return string_length(strings);
   ...: """
   ...: OPTIONS (
   ...:     library=['gs://ibis-testing-libraries/lodash.min.js']
   ...: );
   ...: '''

In [8]: sg.parse_one(sql)
...
ParseError: Invalid expression / Unexpected token. Line 11, Col: 9.
```

**Official Documentation**

https://cloud.google.com/bigquery/docs/user-defined-functions#including-javascript-libraries



Discussion:
I can take a look into this soon
@cpcloud can you double check if the above statement is syntactically correct? Based on BQ's [docs](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement), it seems that when creating JS UDFs, the `OPTIONS` property comes before `AS javascript_code`.

I'm basically trying to see if putting `OPTION` before the `AS ...` part is valid, because that's easier to handle with the existing code.

EDIT: I tried this in BQ's web editor and it worked (same as Phillip's version above):

```sql
  CREATE FUNCTION foo.string_length_0(strings ARRAY<STRING>)
  RETURNS FLOAT64
  LANGUAGE js
  OPTIONS (
      library=['gs://ibis-testing-libraries/lodash.min.js']
  )
  AS """
  'use strict';
  function string_length(strings) {
      return _.sum(_.map(strings, ((x) => x.length)));
  }
  return string_length(strings);
  """

```

Resolving patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -1290,7 +1290,14 @@ def extend_props(temp_props: t.Optional[exp.Properties]) -> None:
             else:
                 begin = self._match(TokenType.BEGIN)
                 return_ = self._match_text_seq("RETURN")
-                expression = self._parse_statement()
+
+                if self._match(TokenType.STRING, advance=False):
+                    # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
+                    # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
+                    expression = self._parse_string()
+                    extend_props(self._parse_properties())
+                else:
+                    expression = self._parse_statement()
 
                 if return_:
                     expression = self.expression(exp.Return, this=expression)


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -721,6 +721,10 @@ def test_user_defined_functions(self):
         self.validate_identity(
             "CREATE TABLE FUNCTION a(x INT64) RETURNS TABLE <q STRING, r INT64> AS SELECT s, t"
         )
+        self.validate_identity(
+            '''CREATE TEMPORARY FUNCTION string_length_0(strings ARRAY<STRING>) RETURNS FLOAT64 LANGUAGE js AS """'use strict'; function string_length(strings) { return _.sum(_.map(strings, ((x) => x.length))); } return string_length(strings);""" OPTIONS (library=['gs://ibis-testing-libraries/lodash.min.js'])''',
+            "CREATE TEMPORARY FUNCTION string_length_0(strings ARRAY<STRING>) RETURNS FLOAT64 LANGUAGE js OPTIONS (library=['gs://ibis-testing-libraries/lodash.min.js']) AS '\\'use strict\\'; function string_length(strings) { return _.sum(_.map(strings, ((x) => x.length))); } return string_length(strings);'",
+        )
 
     def test_group_concat(self):
         self.validate_all(