func QueryDB(req *http.Request) interface{} { // get a variable ready to hold the incoming query var query queryStruct // read the incoming html post body and hold it in the query var made above decoder := json.NewDecoder(req.Body) err := decoder.Decode(&query) if err != nil { log.Panic(err) } // what type of query is this, an aggregation or is data being returned? switch { case len(query.SELECT) == 0 && query.COUNT != "": // they want a count! return countQuery(query) case len(query.SELECT) > 0 && query.COUNT == "": // they want some data returned! return selectQuery(query) default: log.Panic(error_.New("Not a valid query! Valid queries must have one of SELECT / COUNT!")) } // should never get here! // NOTE(@adam-hanna): need to implement proper error handling! return nil }
func evalWhereClause(mWhere map[string]interface{}) []uint64 { // create an array to hold the idx's of all the data that meet our search tempKeysMatched := make([][]uint64, 0) // loop through the where clause, applying the appropriate logic where necessary for key, val := range mWhere { switch key { case "$OR": tempKeysMatched = append(tempKeysMatched, evalOrClause(val.([]interface{}))) case "$NOT": tempKeysMatched = append(tempKeysMatched, evalNotClause(val.(map[string]interface{}))) case "$NOR": tempKeysMatched = append(tempKeysMatched, evalNorClause(val.([]interface{}))) default: // check to see if the values of the keys are anything special if testMap(val) { // it's a map! m := val.(map[string]interface{}) // it should only have one key! for key1, val1 := range m { switch key1 { case "$IN": tempKeysMatched = append(tempKeysMatched, evalInClause(key, val1.([]interface{}))) case "$NIN": tempKeysMatched = append(tempKeysMatched, evalNinClause(key, val1.([]interface{}))) default: // ermm... this should never happen.... I NEED AN ADULT! log.Panic(error_.New("Not a valid query! Only $IN and $NIN are valid sub-documents!")) } } } else { // not a map! // the default is treated as an $AND, so add it to the temp index // we will run an intersection on the temp index last. // NOTE(@adam-hanna): we only support strings for now! tempKeysMatched = append(tempKeysMatched, index.QueryIndex(key, val.(string))) } } } // assume that where the user didn't input a logical operator, that an $AND was implied. // therefore, run the interesection... return arrayOperations.SortedIntersectUint64Arr(tempKeysMatched) }
func selectQuery(query queryStruct) []map[string]interface{} { // make an array to hold the return var aReturn []map[string]interface{} // is a group by present? switch query.GROUPBY { case "": // no groupby present. good. they aren't supported in select queries // Loop through the "WHERE" key/vals. No logical operator (i.e. and / or) means "and" (i.e. intersect not union) // write the matching keys to our multi-dimensional array // NOTE(@adam-hanna): what if a field in the where is an ID or not an indexed field? // find the intersection of the idx's // NOTE(@adam-hanna): should be doing a check on query object in separate function // to be sure it meets specs, rather than doing it here? finalKeysMatched := evalWhereClause(query.WHERE) // redimension the return array aReturn = make([]map[string]interface{}, len(finalKeysMatched)) // loop through the idx's that match all of our where clauses, pulling the data for matchedIdx := range finalKeysMatched { // make a map that represents the data to be returned // the keys of this map represent the column names, the vals represent the data points aReturn[matchedIdx] = make(map[string]interface{}) // Finally, grab the data that the user has asked to be returned // NOTE(@adam-hanna): add support for "*" for idx := range query.SELECT { aReturn[matchedIdx][query.SELECT[idx]] = data.GetDataPointByIdx(query.SELECT[idx], finalKeysMatched[matchedIdx]) } } default: // groupby's are not allowed in select queries! // NOTE(@adam-hanna): should be doing a check on query object in separate function // to be sure it meets specs, rather than doing it here? log.Panic(error_.New("Not a valid query! GROUPBY parameters are not allowed in SELECT queries!")) } return aReturn }
func StartCLI(cliFlags *CliFlagsStruct) { app := cli.NewApp() app.Action = func(ctx *cli.Context) { csvConfigFilePath := ctx.GlobalString("config-file-path") if csvConfigFilePath == "" { log.Fatal( error_.New("--config-file-path (or -c) required!"), ) } csvFilePath := ctx.GlobalString("file-path") if csvFilePath == "" { log.Fatal( error_.New("--file-path (or -f) required!"), ) } serverHostname := ctx.GlobalString("server-hostname") serverPort := uint16(ctx.GlobalInt("server-port")) // build the cli struct to send back to main cliFlags.ConfigFilePath = csvConfigFilePath cliFlags.FilePath = csvFilePath cliFlags.ServerHostname = serverHostname cliFlags.ServerPort = serverPort } app.Authors = []cli.Author{ { Email: "*****@*****.**", Name: "Adam Hanna", }, { Email: "*****@*****.**", Name: "Jonathan Barronville", }, } app.Flags = []cli.Flag{ cli.StringFlag{ Name: "config-file-path,c", Usage: "Path to the JSON config file for the data set.", }, cli.StringFlag{ Name: "file-path,f", Usage: "Path to the CSV file to load.", }, cli.StringFlag{ Name: "server-hostname,n", Usage: "Server hostname.", Value: "localhost", }, cli.IntFlag{ Name: "server-port,p", Usage: "Server port.", Value: 38216, }, } app.Name = "PDQdb" app.Usage = "A read-optimized, in-memory, data processing engine." app.Version = "0.0.1" err := app.Run(os.Args) if err != nil { log.Fatal(err) } }