Repository: cloudspannerecosystem/harbourbridge (go)

Issue:
Use UNIQUE + NOT NULL when there is no primary key
Currently, if a table does not have a primary key, we create a synthetic primary key.

However, some tables have columns that are UNIQUE and NOT NULL but are not primary keys. Such columns would be good candidates to use as the primary key (and better than adding synthetic keys).



Pull request:
using unique keys instead of synth keys if unique key exist
Fixes #35 - Use UNIQUE key if PRIMARY key is absent and UNIQUE key is present.

This change checks if primary key exists for a table, if not ,it further checks if a UNIQUE key exists, if yes then it uses the UNIQUE key as PRIMARY key instead of creating a synthetic primary key.


Patch:
diff --git a/README.md b/README.md
index e2a26d0..ecbd275 100644
--- a/README.md
+++ b/README.md
@@ -123,34 +123,39 @@ variable if it is not already configured using
 
 ### Installing HarbourBridge
 
-Download the tool to your machine and install it.
+You can make a copy of the HarbourBridge codebase from the github repository
+and use "go run":
 
 ```sh
-GO111MODULE=on go get github.com/cloudspannerecosystem/harbourbridge
+git clone https://github.com/cloudspannerecosystem/harbourbridge
+cd harbourbridge
+go run github.com/cloudspannerecosystem/harbourbridge -driver=pg_dump < my_pg_dump_file
+```
+
+```sh
+alias harbourbridge="go run github.com/cloudspannerecosystem/harbourbridge"
 ```
 
-The tool should now be installed as `$GOPATH/bin/harbourbridge`.
-See the [Troubleshooting Guide](#troubleshooting-guide) if you run
-into any issues.
+This workflow also allows you to modify or customize the HarbourBridge codebase.
 
 ### Running HarbourBridge
 
 To use the tool on a PostgreSQL database called mydb, run
 
 ```sh
-pg_dump mydb | $GOPATH/bin/harbourbridge -driver=pg_dump
+pg_dump mydb | harbourbridge -driver=pg_dump
 ```
 
 To use the tool on a MySQL database called mydb, run
 
 ```sh
-mysqldump mydb | $GOPATH/bin/harbourbridge -driver=mysqldump
+mysqldump mydb | harbourbridge -driver=mysqldump
 ```
 
 To use the tool on a DynamoDB database, run
 
 ```sh
-$GOPATH/bin/harbourbridge -driver=dynamodb
+harbourbridge -driver=dynamodb
 ```
 More details on running harbourbridge can be found in [Example usage](#example-usage) section.
 
@@ -189,7 +194,7 @@ example. To use HarbourBridge on cart.pg_dump, download the file locally and
 run
 
 ```
-$GOPATH/bin/harbourbridge -driver=pg_dump < cart.pg_dump
+harbourbridge -driver=pg_dump < cart.pg_dump
 ```
 
 ### Verifying Results
@@ -310,6 +315,11 @@ Spanner DDL files.
 `-session` Specifies a session file that contains all schema and data 
 conversion state endcoded as JSON.
 
+`-dump-file` Specifies the path where the dump file is stored. 
+If the location is Google Cloud Storage (GCS), you can use the following format:
+`-dump-file=gs://{bucket_name}/{path/to/dump_file}`. Please ensure you have
+read pemissions to the GCS bucket you would like to use. 
+
 ## Example Usage
 
 Details on HarbourBridge example usage can be found here: 
@@ -338,27 +348,6 @@ data conversion can be found here:
 
 ## Troubleshooting Guide
 
-HarbourBridge is written using the Go module system, and so it must be
-installed in [module-aware mode](https://golang.org/cmd/go/#hdr-Module_support).
-This can be achived by setting the environment variable `GO111MODULE` to `on`
-before calling "go get" e.g.
-
-```sh
-GO111MODULE=on go get github.com/cloudspannerecosystem/harbourbridge
-```
-
-As an alternative to "go get", you can make a copy of the HarbourBridge
-codebase from the github repository and use "go run":
-
-```sh
-git clone https://github.com/cloudspannerecosystem/harbourbridge
-cd harbourbridge
-pg_dump mydb | go run github.com/cloudspannerecosystem/harbourbridge -driver=pg_dump
-
-```
-
-This workflow also allows you to modify or customize the HarbourBridge codebase.
-
 The following steps can help diagnose common issues encountered while running
 HarbourBridge.
 
diff --git a/conversion/conversion.go b/conversion/conversion.go
index 330c0d3..8535003 100644
--- a/conversion/conversion.go
+++ b/conversion/conversion.go
@@ -33,6 +33,7 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
+	"net/url"
 	"os"
 	"os/exec"
 	"strings"
@@ -55,6 +56,8 @@ import (
 	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
 	instancepb "google.golang.org/genproto/googleapis/spanner/admin/instance/v1"
 
+	"cloud.google.com/go/storage"
+
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/sources/common"
@@ -270,14 +273,81 @@ type IOStreams struct {
 	BytesRead           int64
 }
 
+// downloadFromGCS returns the dump file that is downloaded from GCS
+func downloadFromGCS(bucketName string, filePath string) (*os.File, error) {
+	ctx := context.Background()
+
+	client, err := storage.NewClient(ctx)
+	if err != nil {
+		fmt.Printf("Failed to create GCS client for bucket %q", bucketName)
+		log.Fatal(err)
+	}
+	defer client.Close()
+
+	bucket := client.Bucket(bucketName)
+	rc, err := bucket.Object(filePath).NewReader(ctx)
+	if err != nil {
+		fmt.Printf("readFile: unable to open file from bucket %q, file %q: %v", bucketName, filePath, err)
+		log.Fatal(err)
+		return nil, err
+	}
+	defer rc.Close()
+	r := bufio.NewReader(rc)
+
+	tmpfile, err := ioutil.TempFile("", "harbourbridge.gcs.data")
+	if err != nil {
+		fmt.Printf("saveFile: unable to open temporary file to save dump file from GCS bucket %v", err)
+		log.Fatal(err)
+		return nil, err
+	}
+	syscall.Unlink(tmpfile.Name()) // File will be deleted when this process exits.
+
+	fmt.Printf("\nDownloading dump file from GCS bucket %s, path %s\n", bucketName, filePath)
+	buffer := make([]byte, 1024)
+	for {
+		// read a chunk
+		n, err := r.Read(buffer[:cap(buffer)])
+
+		if err != nil && err != io.EOF {
+			fmt.Printf("readFile: unable to read entire dump file from bucket %s, file %s: %v", bucketName, filePath, err)
+			log.Fatal(err)
+			return nil, err
+		}
+		if n == 0 && err == io.EOF {
+			break
+		}
+
+		// write a chunk
+		if _, err = tmpfile.Write(buffer[:n]); err != nil {
+			fmt.Printf("saveFile: unable to save read data from bucket %s, file %s: %v", bucketName, filePath, err)
+			log.Fatal(err)
+		}
+	}
+
+	return tmpfile, nil
+}
+
 // NewIOStreams returns a new IOStreams struct such that input stream is set
 // to open file descriptor for dumpFile if driver is PGDUMP or MYSQLDUMP.
 // Input stream defaults to stdin. Output stream is always set to stdout.
 func NewIOStreams(driver string, dumpFile string) IOStreams {
 	io := IOStreams{In: os.Stdin, Out: os.Stdout}
+	u, err := url.Parse(dumpFile)
+	if err != nil {
+		fmt.Printf("parseFilePath: unable parse file path for dumpfile %s", dumpFile)
+		log.Fatal(err)
+	}
 	if (driver == constants.PGDUMP || driver == constants.MYSQLDUMP) && dumpFile != "" {
 		fmt.Printf("\nLoading dump file from path: %s\n", dumpFile)
-		f, err := os.Open(dumpFile)
+		var f *os.File
+		var err error
+		if u.Scheme == "gs" {
+			bucketName := u.Host
+			filePath := u.Path[1:] // removes "/" from beginning of path
+			f, err = downloadFromGCS(bucketName, filePath)
+		} else {
+			f, err = os.Open(dumpFile)
+		}
 		if err != nil {
 			fmt.Printf("\nError reading dump file: %v err:%v\n", dumpFile, err)
 			log.Fatal(err)
@@ -497,7 +567,7 @@ func CreateDatabase(ctx context.Context, adminClient *database.DatabaseAdminClie
 		// a) backticks around the database name, and
 		// b) DDL statements as part of a CreateDatabase operation (so schema
 		// must be set using a separate UpdateDatabase operation).
-		req.CreateStatement = "CREATE DATABASE " + dbName
+		req.CreateStatement = "CREATE DATABASE \"" + dbName + "\""
 		req.DatabaseDialect = adminpb.DatabaseDialect_POSTGRESQL
 	} else {
 		req.CreateStatement = "CREATE DATABASE `" + dbName + "`"
diff --git a/go.mod b/go.mod
index 41b24cf..68e5515 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,9 @@ module github.com/cloudspannerecosystem/harbourbridge
 go 1.13
 
 require (
-	cloud.google.com/go v0.93.3
+	cloud.google.com/go v0.94.1
 	cloud.google.com/go/spanner v1.10.0
+	cloud.google.com/go/storage v1.17.0
 	github.com/DATA-DOG/go-sqlmock v1.4.1
 	github.com/aws/aws-sdk-go v1.34.5
 	github.com/go-sql-driver/mysql v1.5.0
@@ -21,8 +22,8 @@ require (
 	github.com/stretchr/testify v1.6.1
 	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
 	golang.org/x/tools v0.1.7 // indirect
-	google.golang.org/api v0.54.0
-	google.golang.org/genproto v0.0.0-20210827211047-25e5f791fe06
+	google.golang.org/api v0.57.0
+	google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6
 )
 
 // cloud.google.com/go will upgrade grpc to v1.40.0
diff --git a/go.sum b/go.sum
index cb6b253..d6644a2 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.17.0 h1:CDpe3jS3EiD5nGlbtvyA4EUfkF6k9GMrxLR8+hLmoec=
+cloud.google.com/go/storage v1.17.0/go.mod h1:0wRtHSM3Npk/QJYdwcpRNVRVJlH2OxyWF9Dws3J+MtE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -187,6 +189,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20190930153522-6ce02741cba3 h1:3CYI9xg87xNAD+es02gZxbX/ky4KQeoFBsNOzuoAQZg=
 github.com/google/pprof v0.0.0-20190930153522-6ce02741cba3/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -197,8 +200,9 @@ github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw=
 github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
@@ -558,8 +562,9 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -603,8 +608,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -674,8 +680,9 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
 google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
diff --git a/internal/convert.go b/internal/convert.go
index 91ba61d..acf0ed4 100644
--- a/internal/convert.go
+++ b/internal/convert.go
@@ -36,8 +36,9 @@ type Conv struct {
 	Location       *time.Location // Timezone (for timestamp conversion).
 	sampleBadRows  rowSamples     // Rows that generated errors during conversion.
 	Stats          stats
-	TimezoneOffset string // Timezone offset for timestamp conversion.
-	TargetDb       string // The target database to which HarbourBridge is writing.
+	TimezoneOffset string              // Timezone offset for timestamp conversion.
+	TargetDb       string              // The target database to which HarbourBridge is writing.
+	UniquePKey     map[string][]string // Maps Spanner table name to unique column name being used as primary key (if needed).
 }
 
 type mode int
@@ -140,6 +141,7 @@ func MakeConv() *Conv {
 			Unexpected: make(map[string]int64),
 		},
 		TimezoneOffset: "+00:00", // By default, use +00:00 offset which is equal to UTC timezone
+		UniquePKey:     make(map[string][]string),
 	}
 }
 
@@ -256,12 +258,30 @@ func (conv *Conv) SampleBadRows(n int) []string {
 func (conv *Conv) AddPrimaryKeys() {
 	for t, ct := range conv.SpSchema {
 		if len(ct.Pks) == 0 {
-			k := conv.buildPrimaryKey(t)
-			ct.ColNames = append(ct.ColNames, k)
-			ct.ColDefs[k] = ddl.ColumnDef{Name: k, T: ddl.Type{Name: ddl.Int64}}
-			ct.Pks = []ddl.IndexKey{{Col: k}}
+			primaryKeyPopulated := false
+			// Populating column with unique constraint as primary key in case
+			// table doesn't have primary key and removing the unique index.
+			if len(ct.Indexes) != 0 {
+				for i, index := range ct.Indexes {
+					if index.Unique {
+						for _, indexKey := range index.Keys {
+							ct.Pks = append(ct.Pks, ddl.IndexKey{Col: indexKey.Col, Desc: indexKey.Desc})
+							conv.UniquePKey[t] = append(conv.UniquePKey[t], indexKey.Col)
+						}
+						primaryKeyPopulated = true
+						ct.Indexes = append(ct.Indexes[:i], ct.Indexes[i+1:]...)
+						break
+					}
+				}
+			}
+			if !primaryKeyPopulated {
+				k := conv.buildPrimaryKey(t)
+				ct.ColNames = append(ct.ColNames, k)
+				ct.ColDefs[k] = ddl.ColumnDef{Name: k, T: ddl.Type{Name: ddl.Int64}}
+				ct.Pks = []ddl.IndexKey{{Col: k}}
+				conv.SyntheticPKeys[t] = SyntheticPKey{k, 0}
+			}
 			conv.SpSchema[t] = ct
-			conv.SyntheticPKeys[t] = SyntheticPKey{k, 0}
 		}
 	}
 }
diff --git a/internal/report.go b/internal/report.go
index 44b96cb..5658186 100644
--- a/internal/report.go
+++ b/internal/report.go
@@ -127,9 +127,11 @@ func buildTableReport(conv *Conv, srcTable string, badWrites map[string]int64) t
 	tr.Warnings = warnings
 	if pk, ok := conv.SyntheticPKeys[spTable]; ok {
 		tr.SyntheticPKey = pk.Col
-		tr.Body = buildTableReportBody(conv, srcTable, issues, spSchema, srcSchema, &pk.Col)
+		tr.Body = buildTableReportBody(conv, srcTable, issues, spSchema, srcSchema, &pk.Col, nil)
+	} else if pk, ok := conv.UniquePKey[spTable]; ok {
+		tr.Body = buildTableReportBody(conv, srcTable, issues, spSchema, srcSchema, nil, pk)
 	} else {
-		tr.Body = buildTableReportBody(conv, srcTable, issues, spSchema, srcSchema, nil)
+		tr.Body = buildTableReportBody(conv, srcTable, issues, spSchema, srcSchema, nil, nil)
 	}
 	if !conv.SchemaMode() {
 		fillRowStats(conv, srcTable, badWrites, &tr)
@@ -137,7 +139,7 @@ func buildTableReport(conv *Conv, srcTable string, badWrites map[string]int64) t
 	return tr
 }
 
-func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]SchemaIssue, spSchema ddl.CreateTable, srcSchema schema.Table, syntheticPK *string) []tableReportBody {
+func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]SchemaIssue, spSchema ddl.CreateTable, srcSchema schema.Table, syntheticPK *string, uniquePK []string) []tableReportBody {
 	var body []tableReportBody
 	for _, p := range []struct {
 		heading  string
@@ -161,6 +163,13 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 				l = append(l, fmt.Sprintf("Column '%s' was added because this table didn't have a primary key. Spanner requires a primary key for every table", *syntheticPK))
 			}
 		}
+		if uniquePK != nil {
+			// Warning about using a column with unique constraint as primary key
+			// in case primary key is absent.
+			if p.severity == warning {
+				l = append(l, fmt.Sprintf("UNIQUE constraint on column(s) '%s' replaced with primary key since this table didn't have one. Spanner requires a primary key for every table", strings.Join(uniquePK, ", ")))
+			}
+		}
 		issueBatcher := make(map[SchemaIssue]bool)
 		for _, srcCol := range cols {
 			for _, i := range issues[srcCol] {
diff --git a/sources/dynamodb/README.md b/sources/dynamodb/README.md
index 3641638..b6f1af5 100644
--- a/sources/dynamodb/README.md
+++ b/sources/dynamodb/README.md
@@ -7,8 +7,14 @@ HarbourBridge information see this [README](https://github.com/cloudspannerecosy
 
 ## Example DynamoDB Usage
 
-The following examples assume `harbourbridge` has been added to your PATH
-environment variable.
+The following examples assume `harbourbridge` alias has been setup as
+following.
+
+```sh
+git clone https://github.com/cloudspannerecosystem/harbourbridge
+cd harbourbridge
+alias harbourbridge="go run github.com/cloudspannerecosystem/harbourbridge"
+```
 
 Before running HarbourBridge, make sure that you have
 [set up your AWS credentials/region](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html)
diff --git a/sources/mysql/README.md b/sources/mysql/README.md
index ff177e7..4cbe9ba 100644
--- a/sources/mysql/README.md
+++ b/sources/mysql/README.md
@@ -7,8 +7,14 @@ see this [README](https://github.com/cloudspannerecosystem/harbourbridge#harbour
 
 ## Example MySQL Usage
 
-The following examples assume `harbourbridge` has been added to your PATH
-environment variable.
+The following examples assume `harbourbridge` alias has been setup as
+following.
+
+```sh
+git clone https://github.com/cloudspannerecosystem/harbourbridge
+cd harbourbridge
+alias harbourbridge="go run github.com/cloudspannerecosystem/harbourbridge"
+```
 
 HarbourBridge can either be used with mysqldump or it can be run directly
 on a MySQL database (via go's database/sql package).
diff --git a/sources/postgres/README.md b/sources/postgres/README.md
index 5e2dae7..d2cbd8d 100644
--- a/sources/postgres/README.md
+++ b/sources/postgres/README.md
@@ -7,8 +7,14 @@ see this [README](https://github.com/cloudspannerecosystem/harbourbridge#harbour
 
 ## Example PostgreSQL Usage
 
-The following examples assume `harbourbridge` has been added to your PATH
-environment variable.
+The following examples assume `harbourbridge` alias has been setup as
+following.
+
+```sh
+git clone https://github.com/cloudspannerecosystem/harbourbridge
+cd harbourbridge
+alias harbourbridge="go run github.com/cloudspannerecosystem/harbourbridge"
+```
 
 HarbourBridge can either be used with pg_dump or it can be run directly
 on a PostgreSQL database (via go's database/sql package).
diff --git a/web/README.md b/web/README.md
index e9f66df..a797a7b 100644
--- a/web/README.md
+++ b/web/README.md
@@ -8,8 +8,14 @@ schema assistant is currently in alpha release.
 
 ### Starting web server for HarbourBridge
 
-The following example assume `harbourbridge` has been added to your PATH
-environment variable.
+The following examples assume `harbourbridge` alias has been setup as
+following.
+
+```sh
+git clone https://github.com/cloudspannerecosystem/harbourbridge
+cd harbourbridge
+alias harbourbridge="go run github.com/cloudspannerecosystem/harbourbridge"
+```
 
 HarbourBridge's Web API feature can be used with all the driver modes available,
 using mysql or postgres dump or direct connection.


Test patch:
diff --git a/internal/convert_test.go b/internal/convert_test.go
index aa972c5..82832ab 100644
--- a/internal/convert_test.go
+++ b/internal/convert_test.go
@@ -84,25 +84,187 @@ func TestGetBadRows(t *testing.T) {
 }
 
 func TestAddPrimaryKeys(t *testing.T) {
-	conv := MakeConv()
-	conv.SpSchema["table"] = ddl.CreateTable{
-		Name:     "table",
-		ColNames: []string{"a", "b"},
-		ColDefs: map[string]ddl.ColumnDef{
-			"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
-			"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+	addPrimaryKeyTests := []struct {
+		name           string
+		inputSchema    map[string]ddl.CreateTable
+		expectedSchema map[string]ddl.CreateTable
+		syntheticKey   string
+		uniqueKey      []string
+	}{
+		{
+			name: "primary key already exists",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks: []ddl.IndexKey{{Col: "a"}}}},
+
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks: []ddl.IndexKey{{Col: "a"}},
+				},
+			},
+		},
+		{
+			name: "single unique key as primary key",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: true, Keys: []ddl.IndexKey{{Col: "b"}}}},
+				},
+			},
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{{Col: "b"}},
+					Indexes: []ddl.CreateIndex{}},
+			},
+			uniqueKey: []string{"b"},
+		},
+		{
+			name: "multiple unique key as primary key",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: true, Keys: []ddl.IndexKey{{Col: "a"}, {Col: "b"}}}},
+				},
+			},
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{{Col: "a"}, {Col: "b"}},
+					Indexes: []ddl.CreateIndex{}},
+			},
+			uniqueKey: []string{"a", "b"},
+		},
+		{
+			name: "in case two unique keys are present first one is taken as primary key",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: true, Keys: []ddl.IndexKey{{Col: "a"}}}, {Name: "", Table: "", Unique: true, Keys: []ddl.IndexKey{{Col: "b"}}}},
+				},
+			},
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{{Col: "a"}},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: true, Keys: []ddl.IndexKey{{Col: "b"}}}},
+				},
+			},
+			uniqueKey: []string{"a"},
+		},
+		{
+			name: "unique index doesn't exist so synthetic primary key created",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks:     []ddl.IndexKey{},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: false, Keys: []ddl.IndexKey{{Col: "b"}}}},
+				},
+			},
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b", "synth_id"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a":        {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b":        {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+						"synth_id": {Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+					},
+					Pks:     []ddl.IndexKey{{Col: "synth_id"}},
+					Indexes: []ddl.CreateIndex{{Name: "", Table: "", Unique: false, Keys: []ddl.IndexKey{{Col: "b"}}}},
+				},
+			},
+			syntheticKey: "synth_id",
 		},
-		Pks: []ddl.IndexKey{}}
-	conv.AddPrimaryKeys()
-	e := ddl.CreateTable{
-		Name:     "table",
-		ColNames: []string{"a", "b", "synth_id"},
-		ColDefs: map[string]ddl.ColumnDef{
-			"a":        {Name: "a", T: ddl.Type{Name: ddl.Int64}},
-			"b":        {Name: "b", T: ddl.Type{Name: ddl.Float64}},
-			"synth_id": {Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+		{
+			name: "synthetic key as primary key",
+			inputSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a": {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+					},
+					Pks: []ddl.IndexKey{},
+				},
+			},
+			expectedSchema: map[string]ddl.CreateTable{
+				"table": {
+					Name:     "table",
+					ColNames: []string{"a", "b", "synth_id"},
+					ColDefs: map[string]ddl.ColumnDef{
+						"a":        {Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b":        {Name: "b", T: ddl.Type{Name: ddl.Float64}},
+						"synth_id": {Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+					},
+					Pks: []ddl.IndexKey{{Col: "synth_id"}}},
+			},
+			syntheticKey: "synth_id",
 		},
-		Pks: []ddl.IndexKey{{Col: "synth_id"}}}
-	assert.Equal(t, e, conv.SpSchema["table"])
-	assert.Equal(t, SyntheticPKey{Col: "synth_id", Sequence: 0}, conv.SyntheticPKeys["table"])
+	}
+	for _, tc := range addPrimaryKeyTests {
+		conv := MakeConv()
+		conv.SpSchema = tc.inputSchema
+		conv.AddPrimaryKeys()
+		if tc.expectedSchema != nil {
+			assert.Equal(t, tc.expectedSchema["table"], conv.SpSchema["table"])
+		}
+		if tc.uniqueKey != nil {
+			assert.Equal(t, tc.uniqueKey, conv.UniquePKey["table"])
+		}
+		if tc.syntheticKey != "" {
+			assert.Equal(t, tc.syntheticKey, conv.SyntheticPKeys["table"].Col)
+		}
+	}
 }
diff --git a/sources/mysql/mysqldump_test.go b/sources/mysql/mysqldump_test.go
index f1f0a7d..1111e81 100644
--- a/sources/mysql/mysqldump_test.go
+++ b/sources/mysql/mysqldump_test.go
@@ -359,15 +359,14 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{}}},
 		},
 		{
 			name: "Create table with multiple index keys with different order",
@@ -381,16 +380,14 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}},
-						ddl.CreateIndex{Name: "custom_index2", Table: "test", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: false}, ddl.IndexKey{Col: "a", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index2", Table: "test", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: false}, ddl.IndexKey{Col: "a", Desc: false}}}}}},
 		},
 		{
 			name: "Alter table add index keys",
@@ -424,15 +421,15 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Alter table add index keys",
@@ -445,15 +442,15 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Create index statement",
@@ -487,15 +484,15 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name:  "Create table with mysql schema",
diff --git a/sources/postgres/pgdump_test.go b/sources/postgres/pgdump_test.go
index ad66369..d74f8a0 100644
--- a/sources/postgres/pgdump_test.go
+++ b/sources/postgres/pgdump_test.go
@@ -330,15 +330,15 @@ func TestProcessPgDump(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: true}, ddl.IndexKey{Col: "b", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: true}, ddl.IndexKey{Col: "b", Desc: false}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Create table with unique constraint",
@@ -351,15 +351,15 @@ func TestProcessPgDump(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Alter table add unique constraint",
@@ -372,15 +372,15 @@ func TestProcessPgDump(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name:  "Create table with pg schema",
@@ -998,15 +998,15 @@ func TestProcessPgDumpPGTarget(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: true}, ddl.IndexKey{Col: "b", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "c", Desc: true}, ddl.IndexKey{Col: "b", Desc: false}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Create table with unique constraint",
@@ -1019,15 +1019,15 @@ func TestProcessPgDumpPGTarget(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name: "Alter table add unique constraint",
@@ -1040,15 +1040,15 @@ func TestProcessPgDumpPGTarget(t *testing.T) {
 			expectedSchema: map[string]ddl.CreateTable{
 				"test": ddl.CreateTable{
 					Name:     "test",
-					ColNames: []string{"a", "b", "c", "synth_id"},
+					ColNames: []string{"a", "b", "c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
-						"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
-						"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
-					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}},
-					Indexes: []ddl.CreateIndex{ddl.CreateIndex{Name: "custom_index", Table: "test", Unique: true, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}, ddl.IndexKey{Col: "c", Desc: false}}}}}},
+					Pks:     []ddl.IndexKey{ddl.IndexKey{Col: "b"}, ddl.IndexKey{Col: "c"}},
+					Indexes: []ddl.CreateIndex{},
+				}},
 		},
 		{
 			name:  "Create table with pg schema",