github.com/CiscoM31/godata@v1.0.10/providers/mysql.go (about) 1 package mysql 2 3 import ( 4 . "github.com/CiscoM31/godata" 5 //"database/sql" 6 //"errors" 7 //"github.com/go-sql-driver/mysql" 8 //"strings" 9 ) 10 11 const ( 12 XMLNamespace string = "http://docs.oasis-open.org/odata/ns/edmx" 13 ODataVersion string = "4.0" 14 ) 15 16 var MySQLPrepareMap = map[string]string{ 17 // wrap the input string in wildcard characters for LIKE clauses 18 "contains": "%?%", 19 "endswith": "%?", 20 "startswith": "?%", 21 } 22 23 var MySQLNodeMap = map[string]string{ 24 "eq": "(%s = %s)", 25 "nq": "(%s != %s)", 26 "gt": "(%s > %s)", 27 "ge": "(%s >= %s)", 28 "lt": "(%s < %s)", 29 "le": "(%s <= %s)", 30 "and": "(%s AND %s)", 31 "or": "(%s OR %s)", 32 "not": "(NOT %s)", 33 // "has": "" 34 //"add": "" 35 //"sub": "" 36 //"mul": "" 37 //"div": "" 38 //"mod": "" 39 "contains": "(%s LIKE %s)", 40 "endswith": "(%s LIKE %s)", 41 "startswith": "(%s LIKE %s)", 42 "length": "LENGTH(%s)", 43 "indexof": "LOCATE(%s)", 44 //"substring": "", 45 "tolower": "LOWER(%s)", 46 "toupper": "UPPER(%s)", 47 "trim": "TRIM(%s)", 48 "concat": "CONCAT(%s,%s)", 49 "year": "YEAR(%s)", 50 "month": "MONTH(%s)", 51 "day": "DAY(%s)", 52 "hour": "HOUR(%s)", 53 "minute": "MINUTE(%s)", 54 "second": "SECOND(%s)", 55 "fractionalsecond": "MICROSECOND(%s)", 56 "date": "DATE(%s)", 57 "time": "TIME(%s)", 58 //"totaloffsetminutes": "", 59 "now": "NOW()", 60 //"maxdatetime":"", 61 //"mindatetime":"", 62 //"totalseconds":"", 63 "round": "ROUND(%s)", 64 "floor": "FLOOR(%s)", 65 "ceiling": "CEIL(%s)", 66 //"isof": "", 67 //"cast": "", 68 //"geo.distance": "", 69 //"geo.intersects": "", 70 //"geo.length": "", 71 //"any": "", 72 //"all": "", 73 "null": "NULL", 74 } 75 76 // Struct to hold MySQL connection parameters. 77 type MySQLConnectionParams struct { 78 Database string 79 Hostname string 80 Port string 81 Username string 82 Password string 83 } 84 85 // A provider for GoData using a MySQL backend. Reads requests, converts them 86 // to MySQL queries, and creates a response object to send back to the client. 87 type MySQLGoDataProvider struct { 88 ConnectionParams *MySQLConnectionParams 89 Namespace string 90 Entities map[string]*MySQLGoDataEntity 91 EntitySets map[string]*MySQLGoDataEntitySet 92 Actions map[string]*GoDataAction 93 Functions map[string]*GoDataFunction 94 Metadata *GoDataMetadata 95 } 96 97 type MySQLGoDataEntity struct { 98 TableName string 99 KeyType string 100 PropColMap map[string]string 101 ColPropMap map[string]string 102 EntityType *GoDataEntityType 103 } 104 105 type MySQLGoDataEntitySet struct { 106 Entity *MySQLGoDataEntity 107 EntitySet *GoDataEntitySet 108 } 109 110 // Build an empty MySQL provider. Provide the connection parameters and the 111 // namespace name. 112 func BuildMySQLProvider(cxnParams *MySQLConnectionParams, namespace string) *MySQLGoDataProvider { 113 return &MySQLGoDataProvider{ 114 ConnectionParams: cxnParams, 115 Entities: make(map[string]*MySQLGoDataEntity), 116 EntitySets: make(map[string]*MySQLGoDataEntitySet), 117 } 118 } 119 120 func (p *MySQLGoDataProvider) BuildQuery(r *GoDataRequest) (string, error) { 121 /* 122 setName := r.FirstSegment.Name 123 entitySet := p.EntitySets[setName] 124 tableName := entitySet.Entity.TableName 125 pKeyValue := r.FirstSegment.Identifier 126 127 query := []byte{} 128 params := []string{} 129 130 selectClause, selectParams, selectErr := p.BuildSelectClause(r) 131 if selectErr != nil { 132 return nil, selectErr 133 } 134 query = append(query, selectClause) 135 params = append(params, selectParams) 136 137 fromClause, fromParams, fromErr := p.BuildFromClause(r) 138 if fromErr != nil { 139 return nil, fromErr 140 } 141 query = append(query, selectClause) 142 params = append(params, selectParams) 143 */ 144 return "", NotImplementedError("not implemented") 145 } 146 147 // Build the select clause to begin the query, and also return the values to 148 // send to a prepared statement. 149 func (p *MySQLGoDataProvider) BuildSelectClause(r *GoDataRequest) ([]byte, []string, error) { 150 return nil, nil, NotImplementedError("not implemented") 151 } 152 153 // Build the from clause in the query, and also return the values to send to 154 // the prepared statement. 155 func (p *MySQLGoDataProvider) BuildFromClause(r *GoDataRequest) ([]byte, []string, error) { 156 return nil, nil, NotImplementedError("not implemented") 157 } 158 159 // Build a where clause that can be appended to an SQL query, and also return 160 // the values to send to a prepared statement. 161 func (p *MySQLGoDataProvider) BuildWhereClause(r *GoDataRequest) ([]byte, []string, error) { 162 /* 163 // Builds the WHERE clause recursively using DFS 164 recursiveBuildWhere := func(n *ParseNode) ([]byte, []string, error) { 165 if n.Token.Type == FilterTokenLiteral { 166 // TODO: map to columns 167 return []byte("?"), []byte(n.Token.Value), nil 168 } 169 170 if v, ok := MySQLNodeMap[n.Token.Value]; ok { 171 params := string 172 children := []byte{} 173 // build each child first using DFS 174 for _, child := range n.Children { 175 q, o, err := recursiveBuildWhere(child) 176 if err != nil { 177 return nil, nil, err 178 } 179 children := append(children, q) 180 // make the assumption that all params appear LTR and are never added 181 // out of order 182 params := append(params, o) 183 } 184 // merge together the children and the current node 185 result := fmt.Sprintf(v, children...) 186 return []byte(result), params, nil 187 } else { 188 return nil, nil, NotImplementedError(n.Token.Value + " is not implemented.") 189 } 190 } 191 */ 192 return nil, nil, NotImplementedError("not implemented") 193 } 194 195 // Respond to a GoDataRequest using the MySQL provider. 196 func (p *MySQLGoDataProvider) Response(r *GoDataRequest) *GoDataResponse { 197 198 return nil 199 } 200 201 // Build the $metadata file from the entities in the builder. It creates a 202 // schema with the given namespace name. 203 func (builder *MySQLGoDataProvider) BuildMetadata() *GoDataMetadata { 204 // convert maps to slices for the metadata 205 entitySets := make([]*GoDataEntitySet, len(builder.EntitySets)) 206 for _, v := range builder.EntitySets { 207 entitySets = append(entitySets, v.EntitySet) 208 } 209 entityTypes := make([]*GoDataEntityType, len(builder.Entities)) 210 for _, v := range builder.Entities { 211 entityTypes = append(entityTypes, v.EntityType) 212 } 213 // build the schema 214 container := GoDataEntityContainer{ 215 Name: builder.ConnectionParams.Database, 216 EntitySets: entitySets, 217 } 218 schema := GoDataSchema{ 219 Namespace: builder.Namespace, 220 EntityTypes: entityTypes, 221 EntityContainers: []*GoDataEntityContainer{&container}, 222 } 223 services := GoDataServices{ 224 Schemas: []*GoDataSchema{&schema}, 225 } 226 227 root := GoDataMetadata{ 228 XMLNamespace: XMLNamespace, 229 Version: ODataVersion, 230 DataServices: &services, 231 } 232 233 return &root 234 } 235 236 // Expose a table in the MySQL database as an entity with the given name in the 237 // OData service. 238 func (builder *MySQLGoDataProvider) ExposeEntity(tblname, entityname string) *MySQLGoDataEntity { 239 entitytype := GoDataEntityType{Name: entityname} 240 myentity := &MySQLGoDataEntity{tblname, "", map[string]string{}, map[string]string{}, &entitytype} 241 builder.Entities[entityname] = myentity 242 return myentity 243 } 244 245 // Expose a queryable collection of entities 246 func (builder *MySQLGoDataProvider) ExposeEntitySet(entity *MySQLGoDataEntity, setname string) *MySQLGoDataEntitySet { 247 entityset := &GoDataEntitySet{Name: setname, EntityType: builder.Namespace + "." + entity.EntityType.Name} 248 myset := &MySQLGoDataEntitySet{entity, entityset} 249 builder.EntitySets[setname] = myset 250 return myset 251 } 252 253 // Adds the necessary NavigationProperty tags to entities to expose a one-to-one 254 // relationship from a (One) -> b (One). A column name and corresponding 255 // property name must be provided for each entity. The columns must be foreign 256 // keys corresponding to the primary key in the opposite table. 257 func (builder *MySQLGoDataProvider) ExposeOneToOne(a, b *MySQLGoDataEntity, acol, bcol, aprop, bprop string) { 258 prop := GoDataNavigationProperty{Name: aprop, Type: builder.Namespace + "." + b.EntityType.Name, Partner: bprop} 259 a.EntityType.NavigationProperties = append(a.EntityType.NavigationProperties, &prop) 260 prop2 := GoDataNavigationProperty{Name: bprop, Type: builder.Namespace + "." + a.EntityType.Name, Partner: aprop} 261 b.EntityType.NavigationProperties = append(b.EntityType.NavigationProperties, &prop2) 262 a.PropColMap[aprop] = acol 263 a.ColPropMap[acol] = aprop 264 b.PropColMap[bprop] = bcol 265 b.ColPropMap[bcol] = bprop 266 } 267 268 // Adds the necessary NavigationProperty tags to an entity to expose a many-to-one 269 // relationship from a (Many) -> b (One). A column name must be provided for entity a 270 // which will map to the key in entity b. A reverse property will be added to 271 // entity b to map back to entity a, and does not need an explicit column. 272 func (builder *MySQLGoDataProvider) ExposeManyToOne(a, b *MySQLGoDataEntity, acol, aprop, bprop string) { 273 prop := GoDataNavigationProperty{Name: aprop, Type: builder.Namespace + "." + b.EntityType.Name, Partner: bprop} 274 a.EntityType.NavigationProperties = append(a.EntityType.NavigationProperties, &prop) 275 prop2 := GoDataNavigationProperty{Name: bprop, Type: "Collection(" + builder.Namespace + "." + a.EntityType.Name + ")", Partner: aprop} 276 b.EntityType.NavigationProperties = append(b.EntityType.NavigationProperties, &prop2) 277 // the property name corresponding to the column in a will be given the same name 278 // as the key property in b so that it does not conflict with the property name 279 // given by aprop. A referential constraint will be added to the NavigationProperty 280 // in b that links back to this property in a. 281 constrainedProp := b.EntityType.Key.PropertyRef.Name 282 a.ExposeProperty(acol, constrainedProp, b.KeyType) 283 constraint := GoDataReferentialConstraint{Property: constrainedProp, ReferencedProperty: constrainedProp} 284 prop2.ReferentialConstraints = append(prop2.ReferentialConstraints, &constraint) 285 a.PropColMap[aprop] = acol 286 a.ColPropMap[acol] = aprop 287 } 288 289 // Adds the necessary NavigationProperty tags to entities to expose a many-to-many 290 // relationship from a (Many) -> b (Many). A third table must be provided that has 291 // foreign key mappings to the primary keys in both a & b. Both entities will 292 // be given a reference to each other with the given names in aprop & bprop. 293 func (builder *MySQLGoDataProvider) ExposeManyToMany(a, b *MySQLGoDataEntity, tblname, aprop, bprop string) { 294 prop := GoDataNavigationProperty{Name: aprop, Type: "Collection(" + builder.Namespace + "." + b.EntityType.Name + ")", Partner: bprop} 295 a.EntityType.NavigationProperties = append(a.EntityType.NavigationProperties, &prop) 296 prop2 := GoDataNavigationProperty{Name: bprop, Type: "Collection(" + builder.Namespace + "." + a.EntityType.Name + ")", Partner: aprop} 297 b.EntityType.NavigationProperties = append(b.EntityType.NavigationProperties, &prop2) 298 } 299 300 // Adds a NavigationPropertyBinding to two entity sets that are mapped together 301 // by a relationship. This SHOULD be done for any entity sets for whom their 302 // entities contain a NavigationProperty. 303 func (builder *MySQLGoDataProvider) BindProperty(a, b *MySQLGoDataEntitySet, apath, atarget, bpath, btarget string) { 304 bind := GoDataNavigationPropertyBinding{Path: apath, Target: atarget} 305 a.EntitySet.NavigationPropertyBindings = append(a.EntitySet.NavigationPropertyBindings, &bind) 306 bind2 := GoDataNavigationPropertyBinding{Path: bpath, Target: btarget} 307 b.EntitySet.NavigationPropertyBindings = append(b.EntitySet.NavigationPropertyBindings, &bind2) 308 } 309 310 // Expose a key on an entity returned by MySQLGoDataProvider.ExposeEntity. 311 // This is a necessary step for every entity. You must provide a column in the 312 // database to map to the property name in the OData entity, and the OData 313 // type. 314 func (entity *MySQLGoDataEntity) ExposeKey(colname, propname, t string) { 315 entity.EntityType.Key = &GoDataKey{PropertyRef: &GoDataPropertyRef{Name: propname}} 316 entity.KeyType = t 317 entity.ExposePrimitive(colname, propname, t) 318 } 319 320 // Expose an OData primitive property on an entity. You must provide a 321 // corresponding table column in the database. 322 func (entity *MySQLGoDataEntity) ExposePrimitive(colname, propname, t string) { 323 entity.ExposeProperty(colname, propname, t) 324 } 325 326 // Expose an OData property on an entity. You must provide a 327 // corresponding table column in the database. 328 func (entity *MySQLGoDataEntity) ExposeProperty(colname, propname, t string) { 329 entity.EntityType.Properties = append(entity.EntityType.Properties, &GoDataProperty{Name: propname, Type: t}) 330 entity.PropColMap[propname] = colname 331 entity.ColPropMap[colname] = propname 332 }