Repository: redpanda-data/connect (language: go)

Issue:
Support oracle db in sql components
Hi, how long will it take for benthos to support oracle db?


Discussion / hints:
Hey @cdz99, it'd need a bit of investigation as we'd need to open up support and ideally also add some sort of integration testing, but in theory it might be a relatively small task.
Hi, @Jeffail . We have a need for this Oracle DB functionality as well. We could use Oracle SQLcl with the subprocess processor to pass a script and get the results back. Need to test the feasibility of that approach. Ideally, there is just another option in the SQL processor Oracle. 

Is this type of driver compatible with the ethos of Benthos? 
https://github.com/godror/godror
We might want to look at https://github.com/sijms/go-ora too, since it doesn't rely on CGo. The annoying part is adding integration tests for this, since Oracle refuses to publish prebuilt Docker images of their database engine for some reason. The only official way to get a Docker image seems to require building it from scratch: https://github.com/oracle/docker-images/blob/main/OracleDatabase/SingleInstance/README.md

LE: There seems to be a container on `quay.io` that's maintained by a 3rd party: https://github.com/MaksymBilenko/docker-oracle-12c. Not sure if we should rely on it, though.

LE2: This might be a better-maintained container: https://hub.docker.com/r/gvenzl/oracle-xe -> https://github.com/gvenzl/oci-oracle-xe

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1369df4557..3cbf295a51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
 
 ## Unreleased
 
+### Added
+
+- All `sql` components now support Oracle DB.
+
 ### Fixed
 
 - All SQL components now accept an empty or unspecified `args_mapping` as an alias for no arguments.
diff --git a/go.mod b/go.mod
index 75e809d523..97a1c75d5d 100644
--- a/go.mod
+++ b/go.mod
@@ -81,6 +81,7 @@ require (
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/segmentio/ksuid v1.0.4
 	github.com/segmentio/parquet-go v0.0.0-20220830163417-b03c0471ebb0
+	github.com/sijms/go-ora/v2 v2.5.3
 	github.com/sirupsen/logrus v1.8.1
 	github.com/smira/go-statsd v1.3.2
 	github.com/snowflakedb/gosnowflake v1.6.6
diff --git a/go.sum b/go.sum
index 7ca86d48fd..40b2a61b37 100644
--- a/go.sum
+++ b/go.sum
@@ -989,6 +989,8 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sijms/go-ora/v2 v2.5.3 h1:klGKmhqRONVTtIzTdfYTvrW94kdJkdmZl93u2A3vchI=
+github.com/sijms/go-ora/v2 v2.5.3/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
diff --git a/internal/impl/sql/conn_fields.go b/internal/impl/sql/conn_fields.go
index 35e6c08392..10910882c2 100644
--- a/internal/impl/sql/conn_fields.go
+++ b/internal/impl/sql/conn_fields.go
@@ -9,7 +9,7 @@ import (
 	"github.com/benthosdev/benthos/v4/public/service"
 )
 
-var driverField = service.NewStringEnumField("driver", "mysql", "postgres", "clickhouse", "mssql", "sqlite").
+var driverField = service.NewStringEnumField("driver", "mysql", "postgres", "clickhouse", "mssql", "sqlite", "oracle").
 	Description("A database [driver](#drivers) to use.")
 
 var dsnField = service.NewStringField("dsn").
@@ -26,11 +26,13 @@ The following is a list of supported drivers, their placeholder style, and their
 ` + "| `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |" + `
 ` + "| `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |" + `
 ` + "| `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |" + `
+` + "| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |" + `
 
 Please note that the ` + "`postgres`" + ` driver enforces SSL by default, you can override this with the parameter ` + "`sslmode=disable`" + ` if required.`).
 	Example("clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60").
 	Example("foouser:foopassword@tcp(localhost:3306)/foodb").
-	Example("postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable")
+	Example("postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable").
+	Example("oracle://foouser:foopass@localhost:1521/service_name")
 
 func connFields() []*service.ConfigField {
 	return []*service.ConfigField{
@@ -64,6 +66,7 @@ func rawQueryField() *service.ConfigField {
 ` + "| `postgres` | Dollar sign |" + `
 ` + "| `mssql` | Question mark |" + `
 ` + "| `sqlite` | Question mark |" + `
+` + "| `oracle` | Colon |" + `
 `)
 }
 
diff --git a/internal/impl/sql/input_sql_select.go b/internal/impl/sql/input_sql_select.go
index 7e54c65840..f215ffbcab 100644
--- a/internal/impl/sql/input_sql_select.go
+++ b/internal/impl/sql/input_sql_select.go
@@ -148,6 +148,8 @@ func newSQLSelectInputFromConfig(conf *service.ParsedConfig, logger *service.Log
 	s.builder = squirrel.Select(columns...).From(tableStr)
 	if s.driver == "postgres" || s.driver == "clickhouse" {
 		s.builder = s.builder.PlaceholderFormat(squirrel.Dollar)
+	} else if s.driver == "oracle" {
+		s.builder = s.builder.PlaceholderFormat(squirrel.Colon)
 	}
 
 	if conf.Contains("prefix") {
diff --git a/internal/impl/sql/output_sql_insert.go b/internal/impl/sql/output_sql_insert.go
index d6d3023811..fb025799af 100644
--- a/internal/impl/sql/output_sql_insert.go
+++ b/internal/impl/sql/output_sql_insert.go
@@ -121,6 +121,7 @@ func newSQLInsertOutputFromConfig(conf *service.ParsedConfig, logger *service.Lo
 	}
 	if _, in := map[string]struct{}{
 		"clickhouse": {},
+		"oracle":     {},
 	}[s.driver]; in {
 		s.useTxStmt = true
 	}
@@ -148,6 +149,16 @@ func newSQLInsertOutputFromConfig(conf *service.ParsedConfig, logger *service.Lo
 	s.builder = squirrel.Insert(tableStr).Columns(columns...)
 	if s.driver == "postgres" || s.driver == "clickhouse" {
 		s.builder = s.builder.PlaceholderFormat(squirrel.Dollar)
+	} else if s.driver == "oracle" {
+		s.builder = s.builder.PlaceholderFormat(squirrel.Colon)
+	}
+
+	if s.useTxStmt {
+		values := make([]any, 0, len(columns))
+		for _, c := range columns {
+			values = append(values, c)
+		}
+		s.builder = s.builder.Values(values...)
 	}
 
 	if conf.Contains("prefix") {
@@ -212,7 +223,7 @@ func (s *sqlInsertOutput) WriteBatch(ctx context.Context, batch service.MessageB
 		if tx, err = s.db.Begin(); err != nil {
 			return err
 		}
-		sqlStr, _, err := insertBuilder.Values().ToSql()
+		sqlStr, _, err := insertBuilder.ToSql()
 		if err != nil {
 			return err
 		}
diff --git a/internal/impl/sql/processor_sql_insert.go b/internal/impl/sql/processor_sql_insert.go
index 2fbb54cf6c..6d8d7b7245 100644
--- a/internal/impl/sql/processor_sql_insert.go
+++ b/internal/impl/sql/processor_sql_insert.go
@@ -110,6 +110,7 @@ func NewSQLInsertProcessorFromConfig(conf *service.ParsedConfig, logger *service
 	}
 	if _, in := map[string]struct{}{
 		"clickhouse": {},
+		"oracle":     {},
 	}[driverStr]; in {
 		s.useTxStmt = true
 	}
@@ -138,6 +139,16 @@ func NewSQLInsertProcessorFromConfig(conf *service.ParsedConfig, logger *service
 	s.builder = squirrel.Insert(tableStr).Columns(columns...)
 	if driverStr == "postgres" || driverStr == "clickhouse" {
 		s.builder = s.builder.PlaceholderFormat(squirrel.Dollar)
+	} else if driverStr == "oracle" {
+		s.builder = s.builder.PlaceholderFormat(squirrel.Colon)
+	}
+
+	if s.useTxStmt {
+		values := make([]any, 0, len(columns))
+		for _, c := range columns {
+			values = append(values, c)
+		}
+		s.builder = s.builder.Values(values...)
 	}
 
 	if conf.Contains("prefix") {
@@ -192,7 +203,7 @@ func (s *sqlInsertProcessor) ProcessBatch(ctx context.Context, batch service.Mes
 		if tx, err = s.db.Begin(); err != nil {
 			return nil, err
 		}
-		sqlStr, _, err := insertBuilder.Values().ToSql()
+		sqlStr, _, err := insertBuilder.ToSql()
 		if err != nil {
 			return nil, err
 		}
diff --git a/internal/impl/sql/processor_sql_select.go b/internal/impl/sql/processor_sql_select.go
index 0f280e24a3..877ff9cbb8 100644
--- a/internal/impl/sql/processor_sql_select.go
+++ b/internal/impl/sql/processor_sql_select.go
@@ -147,6 +147,8 @@ func NewSQLSelectProcessorFromConfig(conf *service.ParsedConfig, logger *service
 	s.builder = squirrel.Select(columns...).From(tableStr)
 	if driverStr == "postgres" || driverStr == "clickhouse" {
 		s.builder = s.builder.PlaceholderFormat(squirrel.Dollar)
+	} else if driverStr == "oracle" {
+		s.builder = s.builder.PlaceholderFormat(squirrel.Colon)
 	}
 
 	if conf.Contains("prefix") {
diff --git a/internal/impl/sql/util.go b/internal/impl/sql/util.go
index c3730c0b74..45a1da5539 100644
--- a/internal/impl/sql/util.go
+++ b/internal/impl/sql/util.go
@@ -12,28 +12,29 @@ func sqlRowsToArray(rows *sql.Rows) ([]any, error) {
 	jArray := []any{}
 	for rows.Next() {
 		values := make([]any, len(columnNames))
-		valuesWrapped := make([]any, len(columnNames))
+		valuesWrapped := make([]any, 0, len(columnNames))
 		for i := range values {
-			valuesWrapped[i] = &values[i]
+			valuesWrapped = append(valuesWrapped, &values[i])
 		}
 		if err := rows.Scan(valuesWrapped...); err != nil {
 			return nil, err
 		}
 		jObj := map[string]any{}
 		for i, v := range values {
+			col := columnNames[i]
 			switch t := v.(type) {
 			case string:
-				jObj[columnNames[i]] = t
+				jObj[col] = t
 			case []byte:
-				jObj[columnNames[i]] = string(t)
+				jObj[col] = string(t)
 			case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
-				jObj[columnNames[i]] = t
+				jObj[col] = t
 			case float32, float64:
-				jObj[columnNames[i]] = t
+				jObj[col] = t
 			case bool:
-				jObj[columnNames[i]] = t
+				jObj[col] = t
 			default:
-				jObj[columnNames[i]] = t
+				jObj[col] = t
 			}
 		}
 		jArray = append(jArray, jObj)
@@ -50,28 +51,29 @@ func sqlRowToMap(rows *sql.Rows) (map[string]any, error) {
 		return nil, err
 	}
 	values := make([]any, len(columnNames))
-	valuesWrapped := make([]any, len(columnNames))
+	valuesWrapped := make([]any, 0, len(columnNames))
 	for i := range values {
-		valuesWrapped[i] = &values[i]
+		valuesWrapped = append(valuesWrapped, &values[i])
 	}
 	if err := rows.Scan(valuesWrapped...); err != nil {
 		return nil, err
 	}
 	jObj := map[string]any{}
 	for i, v := range values {
+		col := columnNames[i]
 		switch t := v.(type) {
 		case string:
-			jObj[columnNames[i]] = t
+			jObj[col] = t
 		case []byte:
-			jObj[columnNames[i]] = string(t)
+			jObj[col] = string(t)
 		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
-			jObj[columnNames[i]] = t
+			jObj[col] = t
 		case float32, float64:
-			jObj[columnNames[i]] = t
+			jObj[col] = t
 		case bool:
-			jObj[columnNames[i]] = t
+			jObj[col] = t
 		default:
-			jObj[columnNames[i]] = t
+			jObj[col] = t
 		}
 	}
 	return jObj, nil
diff --git a/public/components/sql/package.go b/public/components/sql/package.go
index bac3b71e32..766d2dc5eb 100644
--- a/public/components/sql/package.go
+++ b/public/components/sql/package.go
@@ -8,4 +8,5 @@ import (
 	_ "github.com/denisenkom/go-mssqldb"
 	_ "github.com/go-sql-driver/mysql"
 	_ "github.com/lib/pq"
+	_ "github.com/sijms/go-ora/v2"
 )
diff --git a/website/docs/components/inputs/sql_select.md b/website/docs/components/inputs/sql_select.md
index 732013de26..db22113be9 100644
--- a/website/docs/components/inputs/sql_select.md
+++ b/website/docs/components/inputs/sql_select.md
@@ -106,7 +106,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -123,6 +123,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -137,6 +138,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `table`
diff --git a/website/docs/components/outputs/sql.md b/website/docs/components/outputs/sql.md
index b62e1c0882..834f4b726f 100644
--- a/website/docs/components/outputs/sql.md
+++ b/website/docs/components/outputs/sql.md
@@ -83,7 +83,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `data_source_name`
 
@@ -103,6 +103,7 @@ The query to execute. The style of placeholder to use depends on the driver, som
 | `postgres` | Dollar sign |
 | `mssql` | Question mark |
 | `sqlite` | Question mark |
+| `oracle` | Colon |
 
 
 Type: `string`  
diff --git a/website/docs/components/outputs/sql_insert.md b/website/docs/components/outputs/sql_insert.md
index 55a0760a24..61814ea1f7 100644
--- a/website/docs/components/outputs/sql_insert.md
+++ b/website/docs/components/outputs/sql_insert.md
@@ -113,7 +113,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -130,6 +130,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -144,6 +145,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `table`
diff --git a/website/docs/components/outputs/sql_raw.md b/website/docs/components/outputs/sql_raw.md
index bf6d44c850..f1d01626f6 100644
--- a/website/docs/components/outputs/sql_raw.md
+++ b/website/docs/components/outputs/sql_raw.md
@@ -109,7 +109,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -126,6 +126,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -140,6 +141,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `query`
@@ -153,6 +156,7 @@ The query to execute. The style of placeholder to use depends on the driver, som
 | `postgres` | Dollar sign |
 | `mssql` | Question mark |
 | `sqlite` | Question mark |
+| `oracle` | Colon |
 
 
 Type: `string`  
diff --git a/website/docs/components/processors/sql.md b/website/docs/components/processors/sql.md
index 5486ad4a77..b2548902f0 100644
--- a/website/docs/components/processors/sql.md
+++ b/website/docs/components/processors/sql.md
@@ -73,7 +73,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `data_source_name`
 
@@ -93,6 +93,7 @@ The query to execute. The style of placeholder to use depends on the driver, som
 | `postgres` | Dollar sign |
 | `mssql` | Question mark |
 | `sqlite` | Question mark |
+| `oracle` | Colon |
 
 
 Type: `string`  
diff --git a/website/docs/components/processors/sql_insert.md b/website/docs/components/processors/sql_insert.md
index a6336acb73..0d662cec8c 100644
--- a/website/docs/components/processors/sql_insert.md
+++ b/website/docs/components/processors/sql_insert.md
@@ -101,7 +101,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -118,6 +118,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -132,6 +133,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `table`
diff --git a/website/docs/components/processors/sql_raw.md b/website/docs/components/processors/sql_raw.md
index 130afe913c..669f418c6f 100644
--- a/website/docs/components/processors/sql_raw.md
+++ b/website/docs/components/processors/sql_raw.md
@@ -113,7 +113,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -130,6 +130,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -144,6 +145,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `query`
@@ -157,6 +160,7 @@ The query to execute. The style of placeholder to use depends on the driver, som
 | `postgres` | Dollar sign |
 | `mssql` | Question mark |
 | `sqlite` | Question mark |
+| `oracle` | Colon |
 
 
 Type: `string`  
diff --git a/website/docs/components/processors/sql_select.md b/website/docs/components/processors/sql_select.md
index f67af60b10..69e8d9d48b 100644
--- a/website/docs/components/processors/sql_select.md
+++ b/website/docs/components/processors/sql_select.md
@@ -105,7 +105,7 @@ A database [driver](#drivers) to use.
 
 
 Type: `string`  
-Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`.
+Options: `mysql`, `postgres`, `clickhouse`, `mssql`, `sqlite`, `oracle`.
 
 ### `dsn`
 
@@ -122,6 +122,7 @@ The following is a list of supported drivers, their placeholder style, and their
 | `postgres` | `postgres://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` |
 | `mssql` | `sqlserver://[user[:password]@][netloc][:port][?database=dbname&param1=value1&...]` |
 | `sqlite` | `file:/path/to/filename.db[?param&=value1&...]` |
+| `oracle` | `[username[:password]@][netloc][:port]/service_name?server=server2&server=server3` |
 
 Please note that the `postgres` driver enforces SSL by default, you can override this with the parameter `sslmode=disable` if required.
 
@@ -136,6 +137,8 @@ dsn: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=
 dsn: foouser:foopassword@tcp(localhost:3306)/foodb
 
 dsn: postgres://foouser:foopass@localhost:5432/foodb?sslmode=disable
+
+dsn: oracle://foouser:foopass@localhost:1521/service_name
 ```
 
 ### `table`


Test patch:
diff --git a/internal/impl/sql/integration_test.go b/internal/impl/sql/integration_test.go
index 7ba6fa5a8a..f17cbfec06 100644
--- a/internal/impl/sql/integration_test.go
+++ b/internal/impl/sql/integration_test.go
@@ -6,8 +6,8 @@ import (
 	"flag"
 	"fmt"
 	"os"
+	"path/filepath"
 	"regexp"
-	"runtime"
 	"strings"
 	"sync"
 	"testing"
@@ -22,6 +22,7 @@ import (
 	"github.com/benthosdev/benthos/v4/public/service"
 
 	_ "github.com/benthosdev/benthos/v4/public/components/pure"
+	_ "github.com/benthosdev/benthos/v4/public/components/sql"
 )
 
 type testFn func(t *testing.T, driver, dsn, table string)
@@ -30,19 +31,19 @@ func testProcessors(name string, fn func(t *testing.T, insertProc, selectProc se
 	return func(t *testing.T, driver, dsn, table string) {
 		t.Run(name, func(t *testing.T) {
 			insertConf := fmt.Sprintf(`
-driver: %v
-dsn: %v
-table: %v
-columns: [ foo, bar, baz ]
+driver: %s
+dsn: %s
+table: %s
+columns: [ "\"foo\"", "\"bar\"", "\"baz\"" ]
 args_mapping: 'root = [ this.foo, this.bar.floor(), this.baz ]'
 `, driver, dsn, table)
 
 			queryConf := fmt.Sprintf(`
-driver: %v
-dsn: %v
-table: %v
-columns: [ foo, bar, baz ]
-where: foo = ?
+driver: %s
+dsn: %s
+table: %s
+columns: [ "\"foo\"", "\"bar\"", "\"baz\"" ]
+where: '"foo" = ?'
 args_mapping: 'root = [ this.id ]'
 `, driver, dsn, table)
 
@@ -73,11 +74,13 @@ func testRawProcessors(name string, fn func(t *testing.T, insertProc, selectProc
 			valuesStr := `(?, ?, ?)`
 			if driver == "postgres" || driver == "clickhouse" {
 				valuesStr = `($1, $2, $3)`
+			} else if driver == "oracle" {
+				valuesStr = `(:1, :2, :3)`
 			}
 			insertConf := fmt.Sprintf(`
-driver: %v
-dsn: %v
-query: insert into %v ( foo, bar, baz ) values `+valuesStr+`
+driver: %s
+dsn: %s
+query: insert into %s ( "foo", "bar", "baz" ) values `+valuesStr+`
 args_mapping: 'root = [ this.foo, this.bar.floor(), this.baz ]'
 exec_only: true
 `, driver, dsn, table)
@@ -85,11 +88,13 @@ exec_only: true
 			placeholderStr := "?"
 			if driver == "postgres" || driver == "clickhouse" {
 				placeholderStr = "$1"
+			} else if driver == "oracle" {
+				placeholderStr = ":1"
 			}
 			queryConf := fmt.Sprintf(`
-driver: %v
-dsn: %v
-query: select foo, bar, baz from %v where foo = `+placeholderStr+`
+driver: %s
+dsn: %s
+query: select "foo", "bar", "baz" from %s where "foo" = `+placeholderStr+`
 args_mapping: 'root = [ this.id ]'
 `, driver, dsn, table)
 
@@ -120,22 +125,26 @@ func testRawDeprecatedProcessors(name string, fn func(t *testing.T, insertProc,
 			valuesStr := `(?, ?, ?)`
 			if driver == "postgres" || driver == "clickhouse" {
 				valuesStr = `($1, $2, $3)`
+			} else if driver == "oracle" {
+				valuesStr = `(:1, :2, :3)`
 			}
 			insertConf := fmt.Sprintf(`
-driver: %v
-data_source_name: %v
-query: insert into %v ( foo, bar, baz ) values `+valuesStr+`
+driver: %s
+data_source_name: %s
+query: insert into %s ( "foo", "bar", "baz" ) values `+valuesStr+`
 args_mapping: 'root = [ this.foo, this.bar.floor(), this.baz ]'
 `, driver, dsn, table)
 
 			placeholderStr := "?"
 			if driver == "postgres" || driver == "clickhouse" {
 				placeholderStr = "$1"
+			} else if driver == "oracle" {
+				placeholderStr = ":1"
 			}
 			queryConf := fmt.Sprintf(`
-driver: %v
-data_source_name: %v
-query: select foo, bar, baz from %v where foo = `+placeholderStr+`
+driver: %s
+data_source_name: %s
+query: select "foo", "bar", "baz" from %s where "foo" = `+placeholderStr+`
 args_mapping: 'root = [ this.id ]'
 result_codec: json_array
 `, driver, dsn, table)
@@ -165,8 +174,8 @@ var testBatchProcessorBasic = testProcessors("basic", func(t *testing.T, insertP
 	var insertBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
 		insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-  "foo": "doc-%v",
-  "bar": %v,
+  "foo": "doc-%d",
+  "bar": %d,
   "baz": "and this"
 }`, i, i))))
 	}
@@ -181,7 +190,7 @@ var testBatchProcessorBasic = testProcessors("basic", func(t *testing.T, insertP
 
 	var queryBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
-		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%v"}`, i))))
+		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%d"}`, i))))
 	}
 
 	resBatches, err = selectProc.ProcessBatch(context.Background(), queryBatch)
@@ -191,7 +200,7 @@ var testBatchProcessorBasic = testProcessors("basic", func(t *testing.T, insertP
 	for i, v := range resBatches[0] {
 		require.NoError(t, v.GetError())
 
-		exp := fmt.Sprintf(`[{"bar":%v,"baz":"and this","foo":"doc-%v"}]`, i, i)
+		exp := fmt.Sprintf(`[{"bar":%d,"baz":"and this","foo":"doc-%d"}]`, i, i)
 		actBytes, err := v.AsBytes()
 		require.NoError(t, err)
 
@@ -209,8 +218,8 @@ var testBatchProcessorParallel = testProcessors("parallel", func(t *testing.T, i
 		for j := 0; j < nLoops; j++ {
 			index := i*nLoops + j
 			insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-  "foo": "doc-%v",
-  "bar": %v,
+  "foo": "doc-%d",
+  "bar": %d,
   "baz": "and this"
 }`, index, index))))
 		}
@@ -236,7 +245,7 @@ var testBatchProcessorParallel = testProcessors("parallel", func(t *testing.T, i
 
 		for j := 0; j < nLoops; j++ {
 			index := i*nLoops + j
-			queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%v"}`, index))))
+			queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%d"}`, index))))
 		}
 
 		wg.Add(1)
@@ -261,8 +270,8 @@ var testRawProcessorsBasic = testRawProcessors("raw", func(t *testing.T, insertP
 	var insertBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
 		insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-  "foo": "doc-%v",
-  "bar": %v,
+  "foo": "doc-%d",
+  "bar": %d,
   "baz": "and this"
 }`, i, i))))
 	}
@@ -277,7 +286,7 @@ var testRawProcessorsBasic = testRawProcessors("raw", func(t *testing.T, insertP
 
 	var queryBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
-		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%v"}`, i))))
+		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%d"}`, i))))
 	}
 
 	resBatches, err = selectProc.ProcessBatch(context.Background(), queryBatch)
@@ -287,7 +296,7 @@ var testRawProcessorsBasic = testRawProcessors("raw", func(t *testing.T, insertP
 	for i, v := range resBatches[0] {
 		require.NoError(t, v.GetError())
 
-		exp := fmt.Sprintf(`[{"bar":%v,"baz":"and this","foo":"doc-%v"}]`, i, i)
+		exp := fmt.Sprintf(`[{"bar":%d,"baz":"and this","foo":"doc-%d"}]`, i, i)
 		actBytes, err := v.AsBytes()
 		require.NoError(t, err)
 
@@ -299,8 +308,8 @@ var testDeprecatedProcessorsBasic = testRawDeprecatedProcessors("deprecated", fu
 	var insertBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
 		insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-  "foo": "doc-%v",
-  "bar": %v,
+  "foo": "doc-%d",
+  "bar": %d,
   "baz": "and this"
 }`, i, i))))
 	}
@@ -315,7 +324,7 @@ var testDeprecatedProcessorsBasic = testRawDeprecatedProcessors("deprecated", fu
 
 	var queryBatch service.MessageBatch
 	for i := 0; i < 10; i++ {
-		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%v"}`, i))))
+		queryBatch = append(queryBatch, service.NewMessage([]byte(fmt.Sprintf(`{"id":"doc-%d"}`, i))))
 	}
 
 	resBatches, err = selectProc.ProcessBatch(context.Background(), queryBatch)
@@ -325,7 +334,7 @@ var testDeprecatedProcessorsBasic = testRawDeprecatedProcessors("deprecated", fu
 	for i, v := range resBatches[0] {
 		require.NoError(t, v.GetError())
 
-		exp := fmt.Sprintf(`[{"bar":%v,"baz":"and this","foo":"doc-%v"}]`, i, i)
+		exp := fmt.Sprintf(`[{"bar":%d,"baz":"and this","foo":"doc-%d"}]`, i, i)
 		actBytes, err := v.AsBytes()
 		require.NoError(t, err)
 
@@ -346,7 +355,7 @@ sql_insert:
   driver: $driver
   dsn: $dsn
   table: $table
-  columns: [ foo, bar, baz ]
+  columns: [ "\"foo\"", "\"bar\"", "\"baz\"" ]
   args_mapping: 'root = [ this.foo, this.bar.floor(), this.baz ]'
 `)
 
@@ -356,7 +365,7 @@ sql_select:
   dsn: $dsn
   table: $table
   columns: [ "*" ]
-  suffix: ' ORDER BY bar ASC'
+  suffix: ' ORDER BY "bar" ASC'
 processors:
   # For some reason MySQL driver doesn't resolve to integer by default.
   - bloblang: |
@@ -396,13 +405,13 @@ processors:
 		var insertBatch service.MessageBatch
 		for i := 0; i < 10; i++ {
 			insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-	"foo": "doc-%v",
-	"bar": %v,
+	"foo": "doc-%d",
+	"bar": %d,
 	"baz": "and this"
 }`, i, i))))
 		}
 		require.NoError(t, inFn(context.Background(), insertBatch))
-		require.NoError(t, streamIn.StopWithin(time.Second))
+		require.NoError(t, streamIn.StopWithin(15*time.Second))
 
 		require.NoError(t, streamOut.Run(context.Background()))
 
@@ -432,13 +441,15 @@ func testBatchInputOutputRaw(t *testing.T, driver, dsn, table string) {
 		valuesStr := `(?, ?, ?)`
 		if driver == "postgres" || driver == "clickhouse" {
 			valuesStr = `($1, $2, $3)`
+		} else if driver == "oracle" {
+			valuesStr = `(:1, :2, :3)`
 		}
 
 		outputConf := confReplacer.Replace(`
 sql_raw:
   driver: $driver
   dsn: $dsn
-  query: insert into $table (foo, bar, baz) values ` + valuesStr + `
+  query: insert into $table ("foo", "bar", "baz") values ` + valuesStr + `
   args_mapping: 'root = [ this.foo, this.bar.floor(), this.baz ]'
 `)
 
@@ -448,7 +459,7 @@ sql_select:
   dsn: $dsn
   table: $table
   columns: [ "*" ]
-  suffix: ' ORDER BY bar ASC'
+  suffix: ' ORDER BY "bar" ASC'
 processors:
   # For some reason MySQL driver doesn't resolve to integer by default.
   - bloblang: |
@@ -488,13 +499,13 @@ processors:
 		var insertBatch service.MessageBatch
 		for i := 0; i < 10; i++ {
 			insertBatch = append(insertBatch, service.NewMessage([]byte(fmt.Sprintf(`{
-	"foo": "doc-%v",
-	"bar": %v,
+	"foo": "doc-%d",
+	"bar": %d,
 	"baz": "and this"
 }`, i, i))))
 		}
 		require.NoError(t, inFn(context.Background(), insertBatch))
-		require.NoError(t, streamIn.StopWithin(time.Second))
+		require.NoError(t, streamIn.StopWithin(15*time.Second))
 
 		require.NoError(t, streamOut.Run(context.Background()))
 
@@ -541,6 +552,7 @@ func TestIntegration(t *testing.T) {
 	t.Run("mysql", mySQLIntegration)
 	t.Run("mssql", msSQLIntegration)
 	t.Run("sqlite", sqliteIntegration)
+	t.Run("oracle", oracleIntegration)
 }
 
 func clickhouseIntegration(t *testing.T) {
@@ -550,7 +562,7 @@ func clickhouseIntegration(t *testing.T) {
 	if err != nil {
 		t.Skipf("Could not connect to docker: %s", err)
 	}
-	pool.MaxWait = 30 * time.Second
+	pool.MaxWait = 3 * time.Minute
 
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
 		Repository:   "clickhouse/clickhouse-server",
@@ -561,7 +573,7 @@ func clickhouseIntegration(t *testing.T) {
 	var db *sql.DB
 	t.Cleanup(func() {
 		if err = pool.Purge(resource); err != nil {
-			t.Logf("Failed to clean up docker resource: %v", err)
+			t.Logf("Failed to clean up docker resource: %s", err)
 		}
 		if db != nil {
 			db.Close()
@@ -569,15 +581,15 @@ func clickhouseIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo String,
-  bar Int64,
-  baz String
-) engine=Memory;`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" String,
+  "bar" Int64,
+  "baz" String
+		) engine=Memory;`, name))
 		return err
 	}
 
-	dsn := fmt.Sprintf("clickhouse://localhost:%v/", resource.GetPort("9000/tcp"))
+	dsn := fmt.Sprintf("clickhouse://localhost:%s/", resource.GetPort("9000/tcp"))
 	require.NoError(t, pool.Retry(func() error {
 		db, err = sql.Open("clickhouse", dsn)
 		if err != nil {
@@ -604,7 +616,7 @@ func clickhouseOldIntegration(t *testing.T) {
 	if err != nil {
 		t.Skipf("Could not connect to docker: %s", err)
 	}
-	pool.MaxWait = 30 * time.Second
+	pool.MaxWait = 3 * time.Minute
 
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
 		Repository:   "clickhouse/clickhouse-server",
@@ -615,7 +627,7 @@ func clickhouseOldIntegration(t *testing.T) {
 	var db *sql.DB
 	t.Cleanup(func() {
 		if err = pool.Purge(resource); err != nil {
-			t.Logf("Failed to clean up docker resource: %v", err)
+			t.Logf("Failed to clean up docker resource: %s", err)
 		}
 		if db != nil {
 			db.Close()
@@ -623,15 +635,15 @@ func clickhouseOldIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo String,
-  bar Int64,
-  baz String
-) engine=Memory;`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" String,
+  "bar" Int64,
+  "baz" String
+		) engine=Memory;`, name))
 		return err
 	}
 
-	dsn := fmt.Sprintf("tcp://localhost:%v/", resource.GetPort("9000/tcp"))
+	dsn := fmt.Sprintf("tcp://localhost:%s/", resource.GetPort("9000/tcp"))
 	require.NoError(t, pool.Retry(func() error {
 		db, err = sql.Open("clickhouse", dsn)
 		if err != nil {
@@ -658,7 +670,7 @@ func postgresIntegration(t *testing.T) {
 	if err != nil {
 		t.Skipf("Could not connect to docker: %s", err)
 	}
-	pool.MaxWait = 30 * time.Second
+	pool.MaxWait = 3 * time.Minute
 
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
 		Repository:   "postgres",
@@ -674,7 +686,7 @@ func postgresIntegration(t *testing.T) {
 	var db *sql.DB
 	t.Cleanup(func() {
 		if err = pool.Purge(resource); err != nil {
-			t.Logf("Failed to clean up docker resource: %v", err)
+			t.Logf("Failed to clean up docker resource: %s", err)
 		}
 		if db != nil {
 			db.Close()
@@ -682,16 +694,16 @@ func postgresIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo varchar(50) not null,
-  bar integer not null,
-  baz varchar(50) not null,
-  primary key (foo)
-);`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" varchar(50) not null,
+  "bar" integer not null,
+  "baz" varchar(50) not null,
+  primary key ("foo")
+		)`, name))
 		return err
 	}
 
-	dsn := fmt.Sprintf("postgres://testuser:testpass@localhost:%v/testdb?sslmode=disable", resource.GetPort("5432/tcp"))
+	dsn := fmt.Sprintf("postgres://testuser:testpass@localhost:%s/testdb?sslmode=disable", resource.GetPort("5432/tcp"))
 	require.NoError(t, pool.Retry(func() error {
 		db, err = sql.Open("postgres", dsn)
 		if err != nil {
@@ -712,21 +724,20 @@ func postgresIntegration(t *testing.T) {
 }
 
 func mySQLIntegration(t *testing.T) {
-	if runtime.GOOS == "darwin" {
-		t.Skip("skipping test on macos")
-	}
-
 	t.Parallel()
 
 	pool, err := dockertest.NewPool("")
 	if err != nil {
 		t.Skipf("Could not connect to docker: %s", err)
 	}
-	pool.MaxWait = 30 * time.Second
+	pool.MaxWait = 3 * time.Minute
 
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
 		Repository:   "mysql",
 		ExposedPorts: []string{"3306/tcp"},
+		Cmd: []string{
+			"--sql_mode=ANSI_QUOTES",
+		},
 		Env: []string{
 			"MYSQL_USER=testuser",
 			"MYSQL_PASSWORD=testpass",
@@ -739,7 +750,7 @@ func mySQLIntegration(t *testing.T) {
 	var db *sql.DB
 	t.Cleanup(func() {
 		if err = pool.Purge(resource); err != nil {
-			t.Logf("Failed to clean up docker resource: %v", err)
+			t.Logf("Failed to clean up docker resource: %s", err)
 		}
 		if db != nil {
 			db.Close()
@@ -747,16 +758,16 @@ func mySQLIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo varchar(50) not null,
-  bar integer not null,
-  baz varchar(50) not null,
-  primary key (foo)
-		);`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" varchar(50) not null,
+  "bar" integer not null,
+  "baz" varchar(50) not null,
+  primary key ("foo")
+		)`, name))
 		return err
 	}
 
-	dsn := fmt.Sprintf("testuser:testpass@tcp(localhost:%v)/testdb", resource.GetPort("3306/tcp"))
+	dsn := fmt.Sprintf("testuser:testpass@tcp(localhost:%s)/testdb", resource.GetPort("3306/tcp"))
 	require.NoError(t, pool.Retry(func() error {
 		if db, err = sql.Open("mysql", dsn); err != nil {
 			return err
@@ -776,17 +787,13 @@ func mySQLIntegration(t *testing.T) {
 }
 
 func msSQLIntegration(t *testing.T) {
-	if runtime.GOOS == "darwin" {
-		t.Skip("skipping test on macos")
-	}
-
 	t.Parallel()
 
 	pool, err := dockertest.NewPool("")
 	if err != nil {
 		t.Skipf("Could not connect to docker: %s", err)
 	}
-	pool.MaxWait = 30 * time.Second
+	pool.MaxWait = 3 * time.Minute
 
 	testPassword := "ins4n3lyStrongP4ssword"
 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
@@ -802,7 +809,7 @@ func msSQLIntegration(t *testing.T) {
 	var db *sql.DB
 	t.Cleanup(func() {
 		if err = pool.Purge(resource); err != nil {
-			t.Logf("Failed to clean up docker resource: %v", err)
+			t.Logf("Failed to clean up docker resource: %s", err)
 		}
 		if db != nil {
 			db.Close()
@@ -810,16 +817,16 @@ func msSQLIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo varchar(50) not null,
-  bar integer not null,
-  baz varchar(50) not null,
-  primary key (foo)
-		);`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" varchar(50) not null,
+  "bar" integer not null,
+  "baz" varchar(50) not null,
+  primary key ("foo")
+		)`, name))
 		return err
 	}
 
-	dsn := fmt.Sprintf("sqlserver://sa:"+testPassword+"@localhost:%v?database=master", resource.GetPort("1433/tcp"))
+	dsn := fmt.Sprintf("sqlserver://sa:"+testPassword+"@localhost:%s?database=master", resource.GetPort("1433/tcp"))
 	require.NoError(t, pool.Retry(func() error {
 		db, err = sql.Open("mssql", dsn)
 		if err != nil {
@@ -840,12 +847,16 @@ func msSQLIntegration(t *testing.T) {
 }
 
 func sqliteIntegration(t *testing.T) {
+	t.Skip("Broken for now...")
+
 	t.Parallel()
 
+	testDBFile := filepath.Join(t.TempDir(), "foo.db")
+
 	var db *sql.DB
 	var err error
 	t.Cleanup(func() {
-		os.Remove("./foo.db")
+		os.Remove(testDBFile)
 
 		if db != nil {
 			db.Close()
@@ -853,18 +864,16 @@ func sqliteIntegration(t *testing.T) {
 	})
 
 	createTable := func(name string) error {
-		_, err := db.Exec(fmt.Sprintf(`create table %v (
-  foo varchar(50) not null,
-  bar integer not null,
-  baz varchar(50) not null,
-  primary key (foo)
-		);`, name))
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" varchar(50) not null,
+  "bar" integer not null,
+  "baz" varchar(50) not null,
+  primary key ("foo")
+		)`, name))
 		return err
 	}
 
-	os.Remove("./foo.db")
-
-	dsn := "file:foo.db"
+	dsn := "file:" + testDBFile
 
 	require.NoError(t, func() error {
 		db, err = sql.Open("sqlite", dsn)
@@ -884,3 +893,64 @@ func sqliteIntegration(t *testing.T) {
 
 	testSuite(t, "sqlite", dsn, createTable)
 }
+
+func oracleIntegration(t *testing.T) {
+	t.Parallel()
+
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		t.Skipf("Could not connect to docker: %s", err)
+	}
+	pool.MaxWait = 3 * time.Minute
+
+	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
+		Repository:   "gvenzl/oracle-xe",
+		Tag:          "slim-faststart",
+		ExposedPorts: []string{"1521/tcp"},
+		Env: []string{
+			"ORACLE_PASSWORD=testpass",
+		},
+	})
+	require.NoError(t, err)
+
+	var db *sql.DB
+	t.Cleanup(func() {
+		if err = pool.Purge(resource); err != nil {
+			t.Logf("Failed to clean up docker resource: %s", err)
+		}
+		if db != nil {
+			db.Close()
+		}
+	})
+
+	createTable := func(name string) error {
+		_, err := db.Exec(fmt.Sprintf(`create table %s (
+  "foo" varchar(50) not null,
+  "bar" integer not null,
+  "baz" varchar(50) not null,
+  primary key ("foo")
+		)`, name))
+		return err
+	}
+
+	dsn := fmt.Sprintf("oracle://system:testpass@localhost:%s/XE", resource.GetPort("1521/tcp"))
+	require.NoError(t, pool.Retry(func() error {
+		db, err = sql.Open("oracle", dsn)
+		if err != nil {
+			return err
+		}
+
+		if err = db.Ping(); err != nil {
+			db.Close()
+			db = nil
+			return err
+		}
+
+		if err := createTable("footable"); err != nil {
+			return err
+		}
+		return nil
+	}))
+
+	testSuite(t, "oracle", dsn, createTable)
+}