Repository: salsita/node-pg-migrate (ts)

Issue:
Support ASC/DESC on index creation
How would one go about creating an index with `(x ASC, y DESC)`? I'd like to create an index over two columns with the latter having a descending definition.

I tried:
```
pgm.createIndex('my_table', ['someField', 'createdAt desc']);
```

However, because field names are quoted, the generated SQL is invalid:
```
Error executing:
CREATE INDEX "my_table_someField_createdAt desc_index" ON "my_table" ("someField", "createdAt desc");
error: column "createdAt desc" does not exist
```

References
- https://www.postgresql.org/docs/8.3/indexes-ordering.html

Pull request:
Allow opclass and sort per column in indexes
Fixes #632

Patch:
diff --git a/docs/indexes.md b/docs/indexes.md
index 55e0622..9fb8645 100644
--- a/docs/indexes.md
+++ b/docs/indexes.md
@@ -7,14 +7,21 @@
 **Arguments:**
 
 - `tablename` _[[Name](migrations.md#type)]_ - name of the table to alter
-- `columns` _[string or array of strings]_ - columns to add to the index
+- `columns` _[string or array of (array of) strings]_ - columns to add to the index with optional operator class (_[Name](migrations.md#type)_) and sort (_string_)
+
+  Examples:
+
+  - `pgm.createIndex('table', 'column')` => `CREATE INDEX ON "table" ("column")`
+  - `pgm.createIndex('table', ['col1', 'col2'])` => `CREATE INDEX ON "table" ("col1", "col2")`
+  - `pgm.createIndex('table', [{ name: 'col1', sort: 'ASC' }], { name: 'col2', sort: 'DESC' }])` => `CREATE INDEX ON "table" ("col1" ASC, "col2" DESC)`
+  - `pgm.createIndex('table', [{ name: 'col1', opclass: { schema: 'schema'; name: 'opclass' }, sort: 'ASC' }])` => `CREATE INDEX ON "table" ("col1" "schema"."opclass" ASC)`
+
 - `options` _[index options]_ - optional options:
   - `name` _[string]_ - name for the index (one will be inferred from table/columns if undefined)
   - `unique` _[boolean]_ - set to true if this is a unique index
   - `where` _[string]_ - raw sql for where clause of index
   - `concurrently` _[boolean]_ - create this index concurrently
   - `ifNotExists` _[bool]_ - default false
-  - `opclass` _[[Name](migrations.md#type)]_ - name of an operator class to use
   - `method` _[string]_ - btree | hash | gist | spgist | gin
   - `include` _[string or array of strings]_ - columns to add to the include clause
 
diff --git a/package-lock.json b/package-lock.json
index bcf95e8..17247fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,9 +37,9 @@
       "dev": true
     },
     "@sinonjs/commons": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz",
-      "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==",
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
+      "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==",
       "dev": true,
       "requires": {
         "type-detect": "4.0.8"
@@ -129,9 +129,9 @@
       "dev": true
     },
     "@types/lodash": {
-      "version": "4.14.158",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz",
-      "integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w==",
+      "version": "4.14.159",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz",
+      "integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==",
       "dev": true
     },
     "@types/mkdirp": {
@@ -150,9 +150,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "14.0.22",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz",
-      "integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g=="
+      "version": "14.0.27",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
+      "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g=="
     },
     "@types/parse-json": {
       "version": "4.0.0",
@@ -206,12 +206,12 @@
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.1.tgz",
-      "integrity": "sha512-3DB9JDYkMrc8Au00rGFiJLK2Ja9CoMP6Ut0sHsXp3ZtSugjNxvSSHTnKLfo4o+QmjYBJqEznDqsG1zj4F2xnsg==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz",
+      "integrity": "sha512-lFb4VCDleFSR+eo4Ew+HvrJ37ZH1Y9ZyE+qyP7EiwBpcCVxwmUc5PAqhShCQ8N8U5vqYydm74nss+a0wrrCErw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "3.7.1",
+        "@typescript-eslint/experimental-utils": "3.8.0",
         "debug": "^4.1.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.0.0",
@@ -220,45 +220,45 @@
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.7.1.tgz",
-      "integrity": "sha512-TqE97pv7HrqWcGJbLbZt1v59tcqsSVpWTOf1AqrWK7n8nok2sGgVtYRuGXeNeLw3wXlLEbY1MKP3saB2HsO/Ng==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.8.0.tgz",
+      "integrity": "sha512-o8T1blo1lAJE0QDsW7nSyvZHbiDzQDjINJKyB44Z3sSL39qBy5L10ScI/XwDtaiunoyKGLiY9bzRk4YjsUZl8w==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/types": "3.7.1",
-        "@typescript-eslint/typescript-estree": "3.7.1",
+        "@typescript-eslint/types": "3.8.0",
+        "@typescript-eslint/typescript-estree": "3.8.0",
         "eslint-scope": "^5.0.0",
         "eslint-utils": "^2.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.7.1.tgz",
-      "integrity": "sha512-W4QV/gXvfIsccN8225784LNOorcm7ch68Fi3V4Wg7gmkWSQRKevO4RrRqWo6N/Z/myK1QAiGgeaXN57m+R/8iQ==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.8.0.tgz",
+      "integrity": "sha512-u5vjOBaCsnMVQOvkKCXAmmOhyyMmFFf5dbkM3TIbg3MZ2pyv5peE4gj81UAbTHwTOXEwf7eCQTUMKrDl/+qGnA==",
       "dev": true,
       "requires": {
         "@types/eslint-visitor-keys": "^1.0.0",
-        "@typescript-eslint/experimental-utils": "3.7.1",
-        "@typescript-eslint/types": "3.7.1",
-        "@typescript-eslint/typescript-estree": "3.7.1",
+        "@typescript-eslint/experimental-utils": "3.8.0",
+        "@typescript-eslint/types": "3.8.0",
+        "@typescript-eslint/typescript-estree": "3.8.0",
         "eslint-visitor-keys": "^1.1.0"
       }
     },
     "@typescript-eslint/types": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.7.1.tgz",
-      "integrity": "sha512-PZe8twm5Z4b61jt7GAQDor6KiMhgPgf4XmUb9zdrwTbgtC/Sj29gXP1dws9yEn4+aJeyXrjsD9XN7AWFhmnUfg==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.8.0.tgz",
+      "integrity": "sha512-8kROmEQkv6ss9kdQ44vCN1dTrgu4Qxrd2kXr10kz2NP5T8/7JnEfYNxCpPkArbLIhhkGLZV3aVMplH1RXQRF7Q==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.1.tgz",
-      "integrity": "sha512-m97vNZkI08dunYOr2lVZOHoyfpqRs0KDpd6qkGaIcLGhQ2WPtgHOd/eVbsJZ0VYCQvupKrObAGTOvk3tfpybYA==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.8.0.tgz",
+      "integrity": "sha512-MTv9nPDhlKfclwnplRNDL44mP2SY96YmPGxmMbMy6x12I+pERcxpIUht7DXZaj4mOKKtet53wYYXU0ABaiXrLw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "3.7.1",
-        "@typescript-eslint/visitor-keys": "3.7.1",
+        "@typescript-eslint/types": "3.8.0",
+        "@typescript-eslint/visitor-keys": "3.8.0",
         "debug": "^4.1.1",
         "glob": "^7.1.6",
         "is-glob": "^4.0.1",
@@ -268,18 +268,18 @@
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "3.7.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.1.tgz",
-      "integrity": "sha512-xn22sQbEya+Utj2IqJHGLA3i1jDzR43RzWupxojbSWnj3nnPLavaQmWe5utw03CwYao3r00qzXfgJMGNkrzrAA==",
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.8.0.tgz",
+      "integrity": "sha512-gfqQWyVPpT9NpLREXNR820AYwgz+Kr1GuF3nf1wxpHD6hdxI62tq03ToomFnDxY0m3pUB39IF7sil7D5TQexLA==",
       "dev": true,
       "requires": {
         "eslint-visitor-keys": "^1.1.0"
       }
     },
     "acorn": {
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
-      "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==",
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+      "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
       "dev": true
     },
     "acorn-jsx": {
@@ -593,9 +593,9 @@
       },
       "dependencies": {
         "get-stream": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
-          "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+          "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
           "dev": true,
           "requires": {
             "pump": "^3.0.0"
@@ -687,9 +687,9 @@
       "dev": true
     },
     "chokidar": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
-      "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
+      "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
       "dev": true,
       "requires": {
         "anymatch": "~3.1.1",
@@ -915,9 +915,9 @@
       },
       "dependencies": {
         "parse-json": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
-          "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz",
+          "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==",
           "dev": true,
           "requires": {
             "@babel/code-frame": "^7.0.0",
@@ -1702,9 +1702,9 @@
       },
       "dependencies": {
         "estraverse": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
-          "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+          "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
           "dev": true
         }
       }
@@ -2451,9 +2451,9 @@
       "dev": true
     },
     "is-regex": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
-      "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+      "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
       "dev": true,
       "requires": {
         "has-symbols": "^1.0.1"
@@ -2681,9 +2681,9 @@
           }
         },
         "get-stream": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
-          "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+          "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
           "dev": true,
           "requires": {
             "pump": "^3.0.0"
@@ -2722,18 +2722,18 @@
       }
     },
     "listr2": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.2.0.tgz",
-      "integrity": "sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.4.1.tgz",
+      "integrity": "sha512-8pYsCZCztr5+KAjReLyBeGhLV0vaQ2Du/eMe/ux9QAfQl7efiWejM1IWjALh0zHIRYuIbhQ8N2KztZ4ci56pnQ==",
       "dev": true,
       "requires": {
-        "chalk": "^4.0.0",
+        "chalk": "^4.1.0",
         "cli-truncate": "^2.1.0",
         "figures": "^3.2.0",
         "indent-string": "^4.0.0",
         "log-update": "^4.0.0",
         "p-map": "^4.0.0",
-        "rxjs": "^6.5.5",
+        "rxjs": "^6.6.0",
         "through": "^2.3.8"
       },
       "dependencies": {
@@ -2777,9 +2777,9 @@
       }
     },
     "livereload-js": {
-      "version": "3.2.3",
-      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.2.3.tgz",
-      "integrity": "sha512-dXL/WgTGI2K3oeYX/z1lngFXSKtjCB2j7n9WSSluoU6/6+N4LWNoMiCBFNgn8dh3luBn4JBajVSvM5I7Xn6SLA==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.1.tgz",
+      "integrity": "sha512-CBu1gTEfzVhlOK1WASKAAJ9Qx1fHECTq0SUB67sfxwQssopTyvzqTlgl+c0h9pZ6V+Fzd2rc510ppuNusg9teQ==",
       "dev": true
     },
     "load-json-file": {
@@ -3005,9 +3005,9 @@
       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
     },
     "mocha": {
-      "version": "8.1.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.0.tgz",
-      "integrity": "sha512-sI0gaI1I/jPVu3KFpnveWGadfe3JNBAENqgTUPgLZAUppu725zS2mrVztzAgIR8DUscuS4doEBTx9LATC+HSeA==",
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.1.tgz",
+      "integrity": "sha512-p7FuGlYH8t7gaiodlFreseLxEmxTgvyG9RgPHODFPySNhwUehu8NIb0vdSt3WFckSneswZ0Un5typYcWElk7HQ==",
       "dev": true,
       "requires": {
         "ansi-colors": "4.1.1",
@@ -3425,9 +3425,9 @@
       }
     },
     "onetime": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
-      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
       "dev": true,
       "requires": {
         "mimic-fn": "^2.1.0"
@@ -3817,9 +3817,9 @@
       "dev": true
     },
     "postgres-date": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz",
-      "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.6.tgz",
+      "integrity": "sha512-o2a4gxeFcox+CgB3Ig/kNHBP23PiEXHCXx7pcIIsvzoNz4qv+lKTyiSkjOXIMNUl12MO/mOYl2K6wR9X5K6Plg==",
       "dev": true
     },
     "postgres-interval": {
@@ -3859,9 +3859,9 @@
       }
     },
     "prismjs": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz",
-      "integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz",
+      "integrity": "sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==",
       "dev": true,
       "requires": {
         "clipboard": "^2.0.0"
@@ -4034,9 +4034,9 @@
       "dev": true
     },
     "registry-auth-token": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz",
-      "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz",
+      "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==",
       "dev": true,
       "requires": {
         "rc": "^1.2.8"
@@ -4117,9 +4117,9 @@
       }
     },
     "rxjs": {
-      "version": "6.6.0",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz",
-      "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==",
+      "version": "6.6.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz",
+      "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==",
       "dev": true,
       "requires": {
         "tslib": "^1.9.0"
diff --git a/package.json b/package.json
index 49afffe..979ef57 100644
--- a/package.json
+++ b/package.json
@@ -51,14 +51,14 @@
   "devDependencies": {
     "@types/chai": "4.2.12",
     "@types/chai-as-promised": "7.1.3",
-    "@types/lodash": "4.14.158",
+    "@types/lodash": "4.14.159",
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "8.0.1",
     "@types/proxyquire": "1.3.28",
     "@types/sinon": "9.0.4",
     "@types/sinon-chai": "3.2.4",
-    "@typescript-eslint/eslint-plugin": "3.7.1",
-    "@typescript-eslint/parser": "3.7.1",
+    "@typescript-eslint/eslint-plugin": "3.8.0",
+    "@typescript-eslint/parser": "3.8.0",
     "chai": "4.2.0",
     "chai-as-promised": "7.1.1",
     "config": "3.3.1",
@@ -75,7 +75,7 @@
     "husky": "4.2.5",
     "json5": "2.1.3",
     "lint-staged": "10.2.11",
-    "mocha": "8.1.0",
+    "mocha": "8.1.1",
     "pg": "8.3.0",
     "prettier": "2.0.5",
     "proxyquire": "2.1.3",
diff --git a/src/migration-builder.ts b/src/migration-builder.ts
index e555230..2db1f42 100644
--- a/src/migration-builder.ts
+++ b/src/migration-builder.ts
@@ -11,7 +11,7 @@
 
 import { createSchemalize } from './utils'
 import { ColumnDefinitions } from './operations/tablesTypes'
-import { DB, MigrationBuilder, MigrationOptions } from './types'
+import { DB, MigrationBuilder, MigrationOptions, Logger } from './types'
 
 import * as domains from './operations/domains'
 import * as extensions from './operations/extensions'
@@ -196,7 +196,7 @@ export default class MigrationBuilderImpl implements MigrationBuilder {
 
   private _useTransaction: boolean
 
-  constructor(db: DB, typeShorthands: ColumnDefinitions | undefined, shouldDecamelize: boolean) {
+  constructor(db: DB, typeShorthands: ColumnDefinitions | undefined, shouldDecamelize: boolean, logger: Logger) {
     this._steps = []
     this._REVERSE_MODE = false
     // by default, all migrations are wrapped in a transaction
@@ -225,6 +225,7 @@ export default class MigrationBuilderImpl implements MigrationBuilder {
       typeShorthands,
       schemalize: createSchemalize(shouldDecamelize, false),
       literal: createSchemalize(shouldDecamelize, true),
+      logger,
     }
 
     // defines the methods that are accessible via pgm in each migrations
diff --git a/src/migration.ts b/src/migration.ts
index be982e3..ebe2948 100644
--- a/src/migration.ts
+++ b/src/migration.ts
@@ -245,7 +245,7 @@ export class Migration implements RunMigration {
   }
 
   apply(direction: MigrationDirection) {
-    const pgm = new MigrationBuilder(this.db, this.typeShorthands, Boolean(this.options.decamelize))
+    const pgm = new MigrationBuilder(this.db, this.typeShorthands, Boolean(this.options.decamelize), this.logger)
     const action = this._getAction(direction)
 
     if (this.down === this.up) {
diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts
index 33ba7c3..35e37f7 100644
--- a/src/operations/indexes.ts
+++ b/src/operations/indexes.ts
@@ -1,15 +1,20 @@
 import _ from 'lodash'
 import { MigrationOptions, Literal } from '../types'
 import { Name } from './generalTypes'
-import { DropIndex, CreateIndex, CreateIndexOptions, DropIndexOptions } from './indexesTypes'
+import { DropIndex, CreateIndex, CreateIndexOptions, DropIndexOptions, IndexColumn } from './indexesTypes'
 
 export { CreateIndex, DropIndex }
 
-function generateIndexName(table: Name, columns: string | string[], options: CreateIndexOptions | DropIndexOptions) {
+function generateIndexName(
+  table: Name,
+  columns: (string | IndexColumn)[],
+  options: CreateIndexOptions | DropIndexOptions,
+  schemalize: Literal,
+) {
   if (options.name) {
     return typeof table === 'object' ? { schema: table.schema, name: options.name } : options.name
   }
-  const cols = _.isArray(columns) ? columns.join('_') : columns
+  const cols = columns.map((col) => schemalize(typeof col === 'string' ? col : col.name)).join('_')
   const uniq = 'unique' in options && options.unique ? '_unique' : ''
   return typeof table === 'object'
     ? {
@@ -19,27 +24,37 @@ function generateIndexName(table: Name, columns: string | string[], options: Cre
     : `${table}_${cols}${uniq}_index`
 }
 
-function generateColumnString(column: string, literal: Literal) {
-  const openingBracketPos = column.indexOf('(')
-  const closingBracketPos = column.indexOf(')')
-  const isFunction = openingBracketPos >= 0 && closingBracketPos > openingBracketPos
-  return isFunction
-    ? column // expression
-    : literal(column) // single column
+function generateColumnString(column: Name, mOptions: MigrationOptions) {
+  const name = mOptions.schemalize(column)
+  const isSpecial = /[. ()]/.test(name)
+  return isSpecial
+    ? name // expression
+    : mOptions.literal(name) // single column
 }
 
-function generateColumnsString(columns: string | string[], literal: Literal) {
-  return _.isArray(columns)
-    ? columns.map((column) => generateColumnString(column, literal)).join(', ')
-    : generateColumnString(columns, literal)
+function generateColumnsString(columns: (string | IndexColumn)[], mOptions: MigrationOptions) {
+  return columns
+    .map((column) =>
+      typeof column === 'string'
+        ? generateColumnString(column, mOptions)
+        : [
+            generateColumnString(column.name, mOptions),
+            column.opclass ? mOptions.literal(column.opclass) : undefined,
+            column.sort,
+          ]
+            .filter((s) => typeof s === 'string' && s !== '')
+            .join(' '),
+    )
+    .join(', ')
 }
 
 export function dropIndex(mOptions: MigrationOptions) {
-  const _drop: DropIndex = (tableName, columns, options = {}) => {
+  const _drop: DropIndex = (tableName, rawColumns, options = {}) => {
     const { concurrently, ifExists, cascade } = options
+    const columns = _.isArray(rawColumns) ? rawColumns.slice() : [rawColumns]
     const concurrentlyStr = concurrently ? ' CONCURRENTLY' : ''
     const ifExistsStr = ifExists ? ' IF EXISTS' : ''
-    const indexName = generateIndexName(tableName, columns, options)
+    const indexName = generateIndexName(tableName, columns, options, mOptions.schemalize)
     const cascadeStr = cascade ? ' CASCADE' : ''
     const indexNameStr = mOptions.literal(indexName)
 
@@ -49,7 +64,7 @@ export function dropIndex(mOptions: MigrationOptions) {
 }
 
 export function createIndex(mOptions: MigrationOptions) {
-  const _create: CreateIndex = (tableName, columns, options = {}) => {
+  const _create: CreateIndex = (tableName, rawColumns, options = {}) => {
     /*
     columns - the column, columns, or expression to create the index on
 
@@ -59,17 +74,35 @@ export function createIndex(mOptions: MigrationOptions) {
     where - where clause
     concurrently -
     ifNotExists - optionally create index
-    opclass - name of an operator class
     options.method -  [ btree | hash | gist | spgist | gin ]
     */
-    const indexName = generateIndexName(typeof tableName === 'object' ? tableName.name : tableName, columns, options)
-    const columnsString = generateColumnsString(columns, mOptions.literal)
+    const columns = _.isArray(rawColumns) ? rawColumns.slice() : [rawColumns]
+    if (options.opclass) {
+      mOptions.logger.warn(
+        "Using opclass is deprecated. You should use it as part of column definition e.g. pgm.createIndex('table', [['column', 'opclass', 'ASC']])",
+      )
+      const lastIndex = columns.length - 1
+      const lastColumn = columns[lastIndex]
+      if (typeof lastColumn === 'string') {
+        columns[lastIndex] = { name: lastColumn, opclass: options.opclass }
+      } else if (lastColumn.opclass) {
+        throw new Error("There is already defined opclass on column, can't override it with global one")
+      } else {
+        columns[lastIndex] = { ...lastColumn, opclass: options.opclass }
+      }
+    }
+    const indexName = generateIndexName(
+      typeof tableName === 'object' ? tableName.name : tableName,
+      columns,
+      options,
+      mOptions.schemalize,
+    )
+    const columnsString = generateColumnsString(columns, mOptions)
     const unique = options.unique ? ' UNIQUE' : ''
     const concurrently = options.concurrently ? ' CONCURRENTLY' : ''
     const ifNotExistsStr = options.ifNotExists ? ' IF NOT EXISTS' : ''
     const method = options.method ? ` USING ${options.method}` : ''
     const where = options.where ? ` WHERE ${options.where}` : ''
-    const opclass = options.opclass ? ` ${mOptions.schemalize(options.opclass)}` : ''
     const include = options.include
       ? ` INCLUDE (${(_.isArray(options.include) ? options.include : [options.include])
           .map(mOptions.literal)
@@ -78,7 +111,7 @@ export function createIndex(mOptions: MigrationOptions) {
     const indexNameStr = mOptions.literal(indexName)
     const tableNameStr = mOptions.literal(tableName)
 
-    return `CREATE${unique} INDEX${concurrently}${ifNotExistsStr} ${indexNameStr} ON ${tableNameStr}${method} (${columnsString}${opclass})${include}${where};`
+    return `CREATE${unique} INDEX${concurrently}${ifNotExistsStr} ${indexNameStr} ON ${tableNameStr}${method} (${columnsString})${include}${where};`
   }
   _create.reverse = dropIndex(mOptions)
   return _create
diff --git a/src/operations/indexesTypes.ts b/src/operations/indexesTypes.ts
index 72a3212..04dd5b8 100644
--- a/src/operations/indexesTypes.ts
+++ b/src/operations/indexesTypes.ts
@@ -1,11 +1,20 @@
 import { Name, DropOptions } from './generalTypes'
 
+export interface IndexColumn {
+  name: string
+  opclass?: Name
+  sort?: 'ASC' | 'DESC'
+}
+
 export interface CreateIndexOptions {
   name?: string
   unique?: boolean
   where?: string
   concurrently?: boolean
   ifNotExists?: boolean
+  /**
+   * @deprecated should be parameter of IndexColumn
+   */
   opclass?: Name
   method?: 'btree' | 'hash' | 'gist' | 'spgist' | 'gin'
   include?: string | string[]
@@ -18,9 +27,12 @@ export interface DropIndexOptions extends DropOptions {
 
 type CreateIndexFn = (
   tableName: Name,
-  columns: string | string[],
+  columns: string | (string | IndexColumn)[],
   options?: CreateIndexOptions & DropIndexOptions,
 ) => string | string[]
 export type CreateIndex = CreateIndexFn & { reverse: CreateIndexFn }
-export type AddIndex = (tableName: Name, columns: string | string[], options?: CreateIndexOptions) => string | string[]
-export type DropIndex = (tableName: Name, columns: string | string[], options?: DropIndexOptions) => string | string[]
+export type DropIndex = (
+  tableName: Name,
+  columns: string | (string | IndexColumn)[],
+  options?: DropIndexOptions,
+) => string | string[]
diff --git a/src/types.ts b/src/types.ts
index dfb7c0c..a26af03 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -128,6 +128,7 @@ export interface MigrationOptions {
   typeShorthands?: tables.ColumnDefinitions
   schemalize: Literal
   literal: Literal
+  logger: Logger
 }
 
 // Note these currently don't contain the parameterized types like


Test patch:
diff --git a/test/indexes-test.ts b/test/indexes-test.ts
index 6a7e909..17ccce5 100644
--- a/test/indexes-test.ts
+++ b/test/indexes-test.ts
@@ -14,7 +14,7 @@ describe('lib/operations/indexes', () => {
       expect(sql2).to.equal('CREATE INDEX "my_table_col_a_col_b_index" ON "my_schema"."my_table" ("col_a", "col_b");')
     })
 
-    it('add opclass option', () => {
+    it('add opclass option (deprecated)', () => {
       const args: CreateIndexParams = [
         'xTable',
         ['yName'],
@@ -27,12 +27,50 @@ describe('lib/operations/indexes', () => {
       ]
       const sql1 = Indexes.createIndex(options1)(...args)
       const sql2 = Indexes.createIndex(options2)(...args)
-      expect(sql1).to.equal('CREATE INDEX "zIndex" ON "xTable" USING gist ("yName" someOpclass) WHERE some condition;')
+      expect(sql1).to.equal(
+        'CREATE INDEX "zIndex" ON "xTable" USING gist ("yName" "someOpclass") WHERE some condition;',
+      )
+      expect(sql2).to.equal(
+        'CREATE INDEX "z_index" ON "x_table" USING gist ("y_name" "some_opclass") WHERE some condition;',
+      )
+    })
+
+    it('add opclass option', () => {
+      const args: CreateIndexParams = [
+        'xTable',
+        [{ name: 'yName', opclass: { schema: 'someSchema', name: 'someOpclass' } }],
+        {
+          method: 'gist',
+          name: 'zIndex',
+          where: 'some condition',
+        },
+      ]
+      const sql1 = Indexes.createIndex(options1)(...args)
+      const sql2 = Indexes.createIndex(options2)(...args)
+      expect(sql1).to.equal(
+        'CREATE INDEX "zIndex" ON "xTable" USING gist ("yName" "someSchema"."someOpclass") WHERE some condition;',
+      )
       expect(sql2).to.equal(
-        'CREATE INDEX "z_index" ON "x_table" USING gist ("y_name" some_opclass) WHERE some condition;',
+        'CREATE INDEX "z_index" ON "x_table" USING gist ("y_name" "some_schema"."some_opclass") WHERE some condition;',
       )
     })
 
+    it('add sort option', () => {
+      const args: CreateIndexParams = [
+        'xTable',
+        [{ name: 'yName', sort: 'DESC' }],
+        {
+          method: 'gist',
+          name: 'zIndex',
+          where: 'some condition',
+        },
+      ]
+      const sql1 = Indexes.createIndex(options1)(...args)
+      const sql2 = Indexes.createIndex(options2)(...args)
+      expect(sql1).to.equal('CREATE INDEX "zIndex" ON "xTable" USING gist ("yName" DESC) WHERE some condition;')
+      expect(sql2).to.equal('CREATE INDEX "z_index" ON "x_table" USING gist ("y_name" DESC) WHERE some condition;')
+    })
+
     it('add include option', () => {
       const args: CreateIndexParams = ['xTable', ['yName'], { name: 'zIndex', include: 'someOtherColumn' }]
       const sql1 = Indexes.createIndex(options1)(...args)
diff --git a/test/utils.ts b/test/utils.ts
index 8581143..bd8e620 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -1,13 +1,16 @@
 import { createSchemalize } from '../src/utils'
+import { MigrationOptions } from '../src/types'
 
-export const options1 = {
+export const options1: MigrationOptions = {
   typeShorthands: {},
   schemalize: createSchemalize(false, false),
   literal: createSchemalize(false, true),
+  logger: console,
 }
 
-export const options2 = {
+export const options2: MigrationOptions = {
   typeShorthands: {},
   schemalize: createSchemalize(true, false),
   literal: createSchemalize(true, true),
+  logger: console,
 }