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  }