Repository: sqlfluff/sqlfluff

Issue:
TypeError when using integer placeholder
### Search before asking

- [X] I searched the [issues](https://github.com/sqlfluff/sqlfluff/issues) and found no similar issues.


### What Happened

An exception occurs when trying to use integer substituents.

### Expected Behaviour

Work without errors.

### Observed Behaviour


An exception occurs:
```
  ...
  File "venv/lib/python3.9/site-packages/sqlfluff/core/linter/linter.py", line 816, in render_file
    return self.render_string(raw_file, fname, config, encoding)
  File "venv/lib/python3.9/site-packages/sqlfluff/core/linter/linter.py", line 787, in render_string
    templated_file, templater_violations = self.templater.process(
  File "venv/lib/python3.9/site-packages/sqlfluff/core/templaters/placeholder.py", line 183, in process
    start_template_pos, start_template_pos + len(replacement), None
TypeError: object of type 'int' has no len()

```

### How to reproduce

1. Create a file `example.sql`:
```
SELECT 1
LIMIT %(capacity)s;
```
2. Copy `.sqlfluff` from the Configuration section
3. Run `sqlfluff lint --dialect postgres example.sql`

### Dialect

postgres

### Version

sqlfluff, version 0.13.1

### Configuration

```
[sqlfluff]
exclude_rules = L031
templater = placeholder

[sqlfluff:templater:placeholder]
param_style = pyformat
capacity = 15
```

### Are you willing to work on and submit a PR to address the issue?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/sqlfluff/sqlfluff/blob/main/CODE_OF_CONDUCT.md)

Support Postgres-style variable substitution
### Search before asking

- [X] I searched the [issues](https://github.com/sqlfluff/sqlfluff/issues) and found no similar issues.


### Description

The Postgres `psql` utility supports flavor of colon-style variable substitution that currently confuses sqlfluff.  E.g.,

```sql
ALTER TABLE name:variable RENAME TO name;
```

Running the above through sqlfluff produces this output:

```
sqlfluff lint --dialect postgres 2.sql
== [2.sql] FAIL
L:   1 | P:   1 |  PRS | Line 1, Position 1: Found unparsable section: 'ALTER
                       | TABLE name:variable RENAME TO name...'
```

### Use case

I would like it if in the above the string "name:variable" were considered a valid table name (and other identifiers similarly).

### Dialect

This applies to the Postgres dialect.

### Are you willing to work on and submit a PR to address the issue?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/sqlfluff/sqlfluff/blob/main/CODE_OF_CONDUCT.md)



Discussion:

This sounds more like a templater feature than a dialect feature. Does psql allow variables to contain SQL fragments, e.g.: `WHERE foo = '3'`?
> This sounds more like a templater feature than a dialect feature.

True!  After looking over the code some, that may well be the right place to implement this.

> Does psql allow variables to contain SQL fragments, e.g.: WHERE foo = '3'?

Yes.  E.g.,

```
% psql -v expression='2 + 2'
psql (14.2, server 10.18)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

db=> select :expression;
 ?column?
----------
        4
(1 row)

db=> select 5:expression;
 ?column?
----------
       54
(1 row)
```

More at the [docs](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-VARIABLES).

Patch:
diff --git a/src/sqlfluff/core/templaters/placeholder.py b/src/sqlfluff/core/templaters/placeholder.py
--- a/src/sqlfluff/core/templaters/placeholder.py
+++ b/src/sqlfluff/core/templaters/placeholder.py
@@ -21,6 +21,8 @@
 KNOWN_STYLES = {
     # e.g. WHERE bla = :name
     "colon": regex.compile(r"(?<![:\w\x5c]):(?P<param_name>\w+)(?!:)", regex.UNICODE),
+    # e.g. WHERE bla = table:name - use with caution as more prone to false positives
+    "colon_nospaces": regex.compile(r":(?P<param_name>\w+)", regex.UNICODE),
     # e.g. WHERE bla = :2
     "numeric_colon": regex.compile(
         r"(?<![:\w\x5c]):(?P<param_name>\d+)", regex.UNICODE
@@ -29,8 +31,10 @@
     "pyformat": regex.compile(
         r"(?<![:\w\x5c])%\((?P<param_name>[\w_]+)\)s", regex.UNICODE
     ),
-    # e.g. WHERE bla = $name
-    "dollar": regex.compile(r"(?<![:\w\x5c])\$(?P<param_name>[\w_]+)", regex.UNICODE),
+    # e.g. WHERE bla = $name or WHERE bla = ${name}
+    "dollar": regex.compile(
+        r"(?<![:\w\x5c])\${?(?P<param_name>[\w_]+)}?", regex.UNICODE
+    ),
     # e.g. WHERE bla = ?
     "question_mark": regex.compile(r"(?<![:\w\x5c])\?", regex.UNICODE),
     # e.g. WHERE bla = $3
@@ -146,7 +150,7 @@ def process(
                 param_name = found_param["param_name"]
             last_literal_length = span[0] - last_pos_raw
             try:
-                replacement = context[param_name]
+                replacement = str(context[param_name])
             except KeyError as err:
                 # TODO: Add a url here so people can get more help.
                 raise SQLTemplaterError(


Test patch:
diff --git a/test/core/templaters/placeholder_test.py b/test/core/templaters/placeholder_test.py
--- a/test/core/templaters/placeholder_test.py
+++ b/test/core/templaters/placeholder_test.py
@@ -79,6 +79,20 @@ def test__templater_raw():
                 city_ids="(1, 2, 3, 45)",
             ),
         ),
+        (
+            """
+            SELECT user_mail, city_id
+            FROM users_data:table_suffix
+            """,
+            "colon_nospaces",
+            """
+            SELECT user_mail, city_id
+            FROM users_data42
+            """,
+            dict(
+                table_suffix="42",
+            ),
+        ),
         (
             """
             SELECT user_mail, city_id
@@ -123,6 +137,8 @@ def test__templater_raw():
             FROM users_data
             WHERE (city_id) IN %(city_id)s
             AND date > %(date)s
+            AND someflag = %(someflag)s
+            LIMIT %(limit)s
             """,
             "pyformat",
             """
@@ -130,10 +146,11 @@ def test__templater_raw():
             FROM users_data
             WHERE (city_id) IN (1, 2, 3, 45)
             AND date > '2020-10-01'
+            AND someflag = False
+            LIMIT 15
             """,
             dict(
-                city_id="(1, 2, 3, 45)",
-                date="'2020-10-01'",
+                city_id="(1, 2, 3, 45)", date="'2020-10-01'", limit=15, someflag=False
             ),
         ),
         (
@@ -142,6 +159,7 @@ def test__templater_raw():
             FROM users_data
             WHERE (city_id) IN $city_id
             AND date > $date
+            OR date = ${date}
             """,
             "dollar",
             """
@@ -149,6 +167,7 @@ def test__templater_raw():
             FROM users_data
             WHERE (city_id) IN (1, 2, 3, 45)
             AND date > '2020-10-01'
+            OR date = '2020-10-01'
             """,
             dict(
                 city_id="(1, 2, 3, 45)",
@@ -221,6 +240,7 @@ def test__templater_raw():
         "colon_simple_substitution",
         "colon_accept_block_at_end",
         "colon_tuple_substitution",
+        "colon_nospaces",
         "question_mark",
         "numeric_colon",
         "pyformat",