github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/cassandra/cql.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package cassandra 22 23 import ( 24 "bytes" 25 "fmt" 26 "strconv" 27 "strings" 28 "text/template" 29 30 "github.com/uber-go/dosa" 31 ) 32 33 const ( 34 keyspace = "Keyspace" 35 table = "Table" 36 values = "Values" 37 columns = "Columns" 38 columnsWithType = "ColumnsWithType" 39 ifNotExist = "IfNotExist" 40 limit = "Limit" 41 conditions = "Conditions" 42 key = "Key" 43 insertTemplate = `INSERT INTO {{.Keyspace}}.{{.Table}} ({{ColumnFunc .Columns ", "}}) VALUES ({{QuestionMark .Values ", "}}){{ExistsFunc .IfNotExist}};` 44 selectTemplate = `SELECT {{ColumnFunc .Columns ", "}} FROM {{.Keyspace}}.{{.Table}}{{WhereFunc .Conditions}}{{ConditionsFunc .Conditions " AND "}}{{LimitFunc .Limit}};` 45 deleteTemplate = `DELETE FROM {{.Keyspace}}.{{.Table}} WHERE {{ConditionsFunc .Conditions " AND "}};` 46 createTemplate = `CREATE TABLE {{.Keyspace}}.{{.Table}} ({{ColumnWithType .ColumnsWithType ", "}}, PRIMARY KEY (({{PrimaryKeyClause .Key}}){{ClusteringKeyFunc .Key}})) WITH {{ClusteringOrderBy .Key}}COMPACTION = {'class':'LeveledCompactionStrategy'}` 47 ) 48 49 var ( 50 funcMap = template.FuncMap{ 51 "ColumnFunc": strings.Join, 52 "QuestionMark": questionMarkFunc, 53 "ExistsFunc": existsFunc, 54 "LimitFunc": limitFunc, 55 "ConditionsFunc": conditionsFunc, 56 "WhereFunc": whereFunc, 57 "ColumnWithType": columnWithTypeFunc, 58 "PrimaryKeyClause": primaryKeyClauseFunc, 59 "ClusteringKeyFunc": clusteringKeyClauseFunc, 60 "ClusteringOrderBy": clusteringOrderByFunc, 61 } 62 insertTmpl = template.Must(template.New("insert").Funcs(funcMap).Parse(insertTemplate)) 63 selectTmpl = template.Must(template.New("select").Funcs(funcMap).Parse(selectTemplate)) 64 deleteTmpl = template.Must(template.New("delete").Funcs(funcMap).Parse(deleteTemplate)) 65 createTmpl = template.Must(template.New("create").Funcs(funcMap).Parse(createTemplate)) 66 ) 67 68 func columnWithTypeFunc(ct []*dosa.ColumnDefinition, sep string) string { 69 cols := make([]string, len(ct)) 70 for i, col := range ct { 71 cols[i] = strconv.Quote(col.Name) + " " + cassandraType(col.Type) 72 } 73 return strings.Join(cols, sep) 74 } 75 76 func primaryKeyClauseFunc(key *dosa.PrimaryKey) string { 77 partkeys := make([]string, len(key.PartitionKeys)) 78 for i, partKey := range key.PartitionKeys { 79 partkeys[i] = strconv.Quote(partKey) 80 } 81 return strings.Join(partkeys, ",") 82 } 83 84 func clusteringKeyClauseFunc(key *dosa.PrimaryKey) string { 85 if len(key.ClusteringKeys) == 0 { 86 return "" 87 } 88 cluskeys := make([]string, len(key.ClusteringKeys)) 89 for i, clusKey := range key.ClusteringKeys { 90 cluskeys[i] = strconv.Quote(clusKey.Name) 91 } 92 return "," + strings.Join(cluskeys, ",") 93 } 94 func clusteringOrderByFunc(key *dosa.PrimaryKey) string { 95 if len(key.ClusteringKeys) == 0 { 96 return "" 97 } 98 obc := make([]string, len(key.ClusteringKeys)) 99 for i, ckc := range key.ClusteringKeys { 100 obc[i] = strconv.Quote(ckc.Name) 101 if ckc.Descending { 102 obc[i] += " DESC" 103 } else { 104 obc[i] += " ASC" 105 } 106 } 107 return "CLUSTERING ORDER BY (" + strings.Join(obc, ",") + ") AND " 108 } 109 110 func questionMarkFunc(qs []interface{}, sep string) string { 111 questions := make([]string, len(qs)) 112 for i := range qs { 113 questions[i] = "?" 114 } 115 return strings.Join(questions, sep) 116 } 117 118 func existsFunc(exists bool) string { 119 if exists { 120 return " IF NOT EXISTS" 121 } 122 return "" 123 } 124 125 func limitFunc(num int) string { 126 if num > 0 { 127 return fmt.Sprintf(" LIMIT %d", num) 128 } 129 return "" 130 } 131 132 func conditionsFunc(conds []*ColumnCondition, sep string) string { 133 cstrs := make([]string, len(conds)) 134 for i, cond := range conds { 135 sign := "=" 136 switch cond.Condition.Op { 137 case dosa.Lt: 138 sign = "<" 139 case dosa.LtOrEq: 140 sign = "<=" 141 case dosa.Gt: 142 sign = ">" 143 case dosa.GtOrEq: 144 sign = ">=" 145 default: 146 sign = "=" 147 } 148 cstrs[i] = fmt.Sprintf("%s%s?", strconv.Quote(cond.Name), sign) 149 } 150 return strings.Join(cstrs, sep) 151 } 152 153 func whereFunc(conds []*ColumnCondition) string { 154 if len(conds) > 0 { 155 return " WHERE " 156 } 157 return "" 158 } 159 160 // Option to compose a cql statement 161 type Option map[string]interface{} 162 163 // OptFunc is the interface to set option 164 type OptFunc func(Option) 165 166 // Keyspace sets the `keyspace` to the cql statement 167 func Keyspace(v string) OptFunc { 168 return func(opt Option) { 169 opt[keyspace] = strconv.Quote(v) 170 } 171 } 172 173 // Table sets the `table` to the cql statement 174 func Table(v string) OptFunc { 175 return func(opt Option) { 176 opt[table] = strconv.Quote(v) 177 } 178 } 179 180 // Columns sets the `columns` clause to the cql statement 181 func Columns(v []string) OptFunc { 182 return func(opt Option) { 183 quoCs := make([]string, len(v)) 184 for i, c := range v { 185 quoCs[i] = strconv.Quote(c) 186 } 187 opt[columns] = quoCs 188 } 189 } 190 191 // PrimaryKey sets the primary key structure for create table 192 func PrimaryKey(pk *dosa.PrimaryKey) OptFunc { 193 return func(opt Option) { 194 opt[key] = pk 195 } 196 } 197 198 // ColumnsWithType sets the column definitions for each column 199 // which is needed by create table 200 func ColumnsWithType(cols []*dosa.ColumnDefinition) OptFunc { 201 return func(opt Option) { 202 opt[columnsWithType] = cols 203 } 204 } 205 206 // Values sets the `values` clause to the cql statement 207 func Values(v interface{}) OptFunc { 208 return func(opt Option) { 209 opt[values] = v 210 } 211 } 212 213 // IfNotExist sets the `if not exist` clause to the cql statement 214 func IfNotExist(v interface{}) OptFunc { 215 return func(opt Option) { 216 opt[ifNotExist] = v 217 } 218 } 219 220 // Limit sets the `limit` to the cql statement 221 func Limit(v interface{}) OptFunc { 222 return func(opt Option) { 223 opt[limit] = v 224 } 225 } 226 227 // Conditions set the `where` clause to the cql statement 228 func Conditions(v interface{}) OptFunc { 229 return func(opt Option) { 230 opt[conditions] = v 231 } 232 } 233 234 // InsertStmt creates insert statement 235 func InsertStmt(opts ...OptFunc) (string, error) { 236 var bb bytes.Buffer 237 option := Option{ 238 ifNotExist: false, 239 } 240 for _, opt := range opts { 241 opt(option) 242 } 243 err := insertTmpl.Execute(&bb, option) 244 return bb.String(), err 245 } 246 247 // SelectStmt creates select statement 248 func SelectStmt(opts ...OptFunc) (string, error) { 249 var bb bytes.Buffer 250 option := Option{ 251 limit: 0, 252 } 253 for _, opt := range opts { 254 opt(option) 255 } 256 err := selectTmpl.Execute(&bb, option) 257 return bb.String(), err 258 } 259 260 // DeleteStmt creates delete statement 261 func DeleteStmt(opts ...OptFunc) (string, error) { 262 var bb bytes.Buffer 263 option := Option{} 264 for _, opt := range opts { 265 opt(option) 266 } 267 err := deleteTmpl.Execute(&bb, option) 268 return bb.String(), err 269 } 270 271 // CreateStmt creates a create statement 272 func CreateStmt(opts ...OptFunc) (string, error) { 273 var bb bytes.Buffer 274 option := Option{} 275 for _, opt := range opts { 276 opt(option) 277 } 278 err := createTmpl.Execute(&bb, option) 279 return bb.String(), err 280 } 281 282 func cassandraType(t dosa.Type) string { 283 switch t { 284 case dosa.TUUID: 285 return "uuid" 286 case dosa.String: 287 return "text" 288 case dosa.Blob: 289 return "blob" 290 case dosa.Int32: 291 return "int" 292 case dosa.Int64: 293 return "bigint" 294 case dosa.Timestamp: 295 return "timestamp" 296 case dosa.Bool: 297 return "boolean" 298 case dosa.Double: 299 return "double" 300 } 301 panic(t) 302 }