package query //package main // //import ( // "fmt" // "strings" //) // ////func main() { //// var r Root //// //err := json.Unmarshal([]byte(jsonInput), &r) //// //if err != nil { //// // panic(err) //// //} //// // //// ////fmt.Println("ARGS:", r) //// //fmt.Println(buildQuery(r)) //// ////fmt.Println("QUERY PART:", qp) //// //// builder := NewQueryBuilder() //// query, err := builder.BuildQuery(r) //// if err != nil { //// fmt.Println("ERROR:", err) //// } //// //// fmt.Println(query) ////} // //type Table string //type Column string //type FilterType string //type EventOrder string //type FetchFilterType string // //const ( // UserOs FilterType = "userOs" // UserBrowser FilterType = "userBrowser" // UserDevice FilterType = "userDevice" // UserCountry FilterType = "userCountry" // UserCity FilterType = "userCity" // UserState FilterType = "userState" // UserId FilterType = "userId" // UserAnonymousId FilterType = "userAnonymousId" // Referrer FilterType = "referrer" // RevId FilterType = "revId" // UserOsIos FilterType = "userOsIos" // UserDeviceIos FilterType = "userDeviceIos" // UserCountryIos FilterType = "userCountryIos" // UserIdIos FilterType = "userIdIos" // UserAnonymousIdIos FilterType = "userAnonymousIdIos" // RevIdIos FilterType = "revIdIos" // Duration FilterType = "duration" // Platform FilterType = "platform" // Metadata FilterType = "metadata" // Issue FilterType = "issue" // EventsCount FilterType = "eventsCount" // UtmSource FilterType = "utmSource" // UtmMedium FilterType = "utmMedium" // UtmCampaign FilterType = "utmCampaign" // ThermalState FilterType = "thermalState" // MainThreadCPU FilterType = "mainThreadCPU" // ViewComponent FilterType = "viewComponent" // LogEvent FilterType = "logEvent" // ClickEvent FilterType = "clickEvent" // MemoryUsage FilterType = "memoryUsage" //) // //const ( // Click FilterType = "click" // Input FilterType = "input" // Location FilterType = "location" // Custom FilterType = "custom" // Request FilterType = "request" // Fetch FilterType = "fetch" // GraphQL FilterType = "graphql" // StateAction FilterType = "stateAction" // Error FilterType = "error" // Tag FilterType = "tag" // ClickMobile FilterType = "clickMobile" // InputMobile FilterType = "inputMobile" // ViewMobile FilterType = "viewMobile" // CustomMobile FilterType = "customMobile" // RequestMobile FilterType = "requestMobile" // ErrorMobile FilterType = "errorMobile" // SwipeMobile FilterType = "swipeMobile" //) // //const ( // EventOrderThen EventOrder = "then" // EventOrderOr EventOrder = "or" // EventOrderAnd EventOrder = "and" //) // //const ( // FetchFilterTypeFetchUrl FilterType = "fetchUrl" // FetchFilterTypeFetchStatusCode FilterType = "fetchStatusCode" // FetchFilterTypeFetchMethod FilterType = "fetchMethod" // FetchFilterTypeFetchDuration FilterType = "fetchDuration" // FetchFilterTypeFetchRequestBody FilterType = "fetchRequestBody" // FetchFilterTypeFetchResponseBody FilterType = "fetchResponseBody" //) // //const ( // OperatorStringIs = "is" // OperatorStringIsAny = "isAny" // OperatorStringOn = "on" // OperatorStringOnAny = "onAny" // OperatorStringIsNot = "isNot" // OperatorStringIsUndefined = "isUndefined" // OperatorStringNotOn = "notOn" // OperatorStringContains = "contains" // OperatorStringNotContains = "notContains" // OperatorStringStartsWith = "startsWith" // OperatorStringEndsWith = "endsWith" //) // //const ( // OperatorMathEq = "=" // OperatorMathLt = "<" // OperatorMathGt = ">" // OperatorMathLe = "<=" // OperatorMathGe = ">=" //) // ////-------------------------------------------------- //// Constants for columns, tables, etc. ////-------------------------------------------------- // //const ( // TableEvents Table = "product_analytics.events" // TableSessions Table = "experimental.sessions" // // ColEventTime Column = "main.created_at" // ColEventName Column = "main.`$event_name`" // ColEventProjectID Column = "main.project_id" // ColEventProperties Column = "main.`$properties`" // ColEventSessionID Column = "main.session_id" // ColEventURLPath Column = "main.url_path" // ColEventStatus Column = "main.status" // // ColSessionID Column = "s.session_id" // ColDuration Column = "s.duration" // ColUserCountry Column = "s.user_country" // ColUserCity Column = "s.user_city" // ColUserState Column = "s.user_state" // ColUserID Column = "s.user_id" // ColUserAnonymousID Column = "s.user_anonymous_id" // ColUserOS Column = "s.user_os" // ColUserBrowser Column = "s.user_browser" // ColUserDevice Column = "s.user_device" // ColUserDeviceType Column = "s.user_device_type" // ColRevID Column = "s.rev_id" // ColBaseReferrer Column = "s.base_referrer" // ColUtmSource Column = "s.utm_source" // ColUtmMedium Column = "s.utm_medium" // ColUtmCampaign Column = "s.utm_campaign" // ColMetadata1 Column = "s.metadata_1" // ColSessionProjectID Column = "s.project_id" // ColSessionIsNotNull Column = "isNotNull(s.duration)" //) // //type Root struct { // StartTimestamp int64 `json:"startTimestamp"` // EndTimestamp int64 `json:"endTimestamp"` // Series []Series `json:"series"` // ProjectID int64 `json:"projectId"` //} // //type Series struct { // SeriesID int64 `json:"seriesId"` // Name string `json:"name"` // Filter SeriesFilter `json:"filter"` //} // //type SeriesFilter struct { // Filters []FilterObj `json:"filters"` // EventsOrder EventOrder `json:"eventsOrder"` //} // //type FilterObj struct { // Key string `json:"key"` // Type FilterType `json:"type"` // IsEvent bool `json:"isEvent"` // Value []string `json:"value"` // Operator string `json:"operator"` // Source string `json:"source"` // Filters []FilterObj `json:"filters"` //} // //// -------------------------------------------------- //func buildQuery(r Root) string { // s := r.Series[0] // // // iterate over series and partition filters // //for _, s := range r.Series { // // sessionFilters, eventFilters := partitionFilters(s.Filter.Filters) // // sessionWhere := buildSessionWhere(sessionFilters) // // eventWhere, seqHaving := buildEventsWhere(eventFilters, s.Filter.EventsOrder) // // fmt.Println("SESSION FILTERS:", sessionFilters) // // fmt.Println("EVENT FILTERS:", eventFilters) // // fmt.Println("SESSION WHERE:", sessionWhere) // // fmt.Println("EVENT WHERE:", eventWhere) // // fmt.Println("SEQ HAVING:", seqHaving) // //} // // sessionFilters, eventFilters := partitionFilters(s.Filter.Filters) // sessionWhere := buildSessionWhere(sessionFilters) // eventWhere, seqHaving := buildEventsWhere(eventFilters, s.Filter.EventsOrder) // // subQuery := fmt.Sprintf( // "SELECT %s,\n"+ // " MIN(%s) AS first_event_ts,\n"+ // " MAX(%s) AS last_event_ts\n"+ // "FROM %s AS main\n"+ // "WHERE main.project_id = %%(project_id)s\n"+ // " AND %s >= toDateTime(%%(start_time)s/1000)\n"+ // " AND %s <= toDateTime(%%(end_time)s/1000)\n"+ // " AND (%s)\n"+ // "GROUP BY %s\n"+ // "HAVING %s", // ColEventSessionID, // ColEventTime, // ColEventTime, // TableEvents, // ColEventTime, // ColEventTime, // strings.Join(eventWhere, " OR "), // ColEventSessionID, // seqHaving, // ) // // joinQuery := fmt.Sprintf( // "SELECT *\n"+ // "FROM %s AS s\n"+ // "INNER JOIN (\n"+ // " SELECT DISTINCT ev.session_id, ev.`$current_url` AS url_path\n"+ // " FROM %s AS ev\n"+ // " WHERE ev.created_at >= toDateTime(%%(start_time)s/1000)\n"+ // " AND ev.created_at <= toDateTime(%%(end_time)s/1000)\n"+ // " AND ev.project_id = %%(project_id)s\n"+ // " AND ev.`$event_name` = 'LOCATION'\n"+ // ") AS extra_event USING (session_id)\n"+ // "WHERE s.project_id = %%(project_id)s\n"+ // " AND %s\n"+ // " AND s.datetime >= toDateTime(%%(start_time)s/1000)\n"+ // " AND s.datetime <= toDateTime(%%(end_time)s/1000)\n", // TableSessions, // TableEvents, // ColSessionIsNotNull, // ) // // if len(sessionWhere) > 0 { // joinQuery += " AND " + strings.Join(sessionWhere, " AND ") + "\n" // } // // main := fmt.Sprintf( // "SELECT s.session_id AS session_id, s.url_path\n"+ // "FROM (\n%s\n) AS f\n"+ // "INNER JOIN (\n%s) AS s\n"+ // " ON (s.session_id = f.session_id)\n", // subQuery, // joinQuery, // ) // // final := fmt.Sprintf( // "SELECT COUNT(DISTINCT url_path) OVER () AS main_count,\n"+ // " url_path AS name,\n"+ // " COUNT(DISTINCT session_id) AS total,\n"+ // " COALESCE(SUM(COUNT(DISTINCT session_id)) OVER (), 0) AS total_count\n"+ // "FROM (\n%s) AS filtered_sessions\n"+ // "GROUP BY url_path\n"+ // "ORDER BY total DESC\n"+ // "LIMIT 200 OFFSET 0;", // main, // ) // // return final //} // //func partitionFilters(filters []FilterObj) (sessionFilters, eventFilters []FilterObj) { // for _, f := range filters { // if f.IsEvent { // eventFilters = append(eventFilters, f) // } else { // sessionFilters = append(sessionFilters, f) // } // } // return //} // //func buildSessionWhere(filters []FilterObj) []string { // var conds []string // for _, f := range filters { // switch f.Type { // case UserCountry: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserCountry, concatValues(f.Value))) // case UserCity: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserCity, concatValues(f.Value))) // case UserState: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserState, concatValues(f.Value))) // case UserId: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserID, concatValues(f.Value))) // case UserAnonymousId: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserAnonymousID, concatValues(f.Value))) // case UserOs: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserOS, concatValues(f.Value))) // case UserBrowser: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserBrowser, concatValues(f.Value))) // case UserDevice: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserDevice, concatValues(f.Value))) // case Platform: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUserDeviceType, concatValues(f.Value))) // case RevId: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColRevID, concatValues(f.Value))) // case Referrer: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColBaseReferrer, concatValues(f.Value))) // case Duration: // if len(f.Value) == 2 { // conds = append(conds, fmt.Sprintf("%s >= '%s'", ColDuration, f.Value[0])) // conds = append(conds, fmt.Sprintf("%s <= '%s'", ColDuration, f.Value[1])) // } // case UtmSource: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUtmSource, concatValues(f.Value))) // case UtmMedium: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUtmMedium, concatValues(f.Value))) // case UtmCampaign: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColUtmCampaign, concatValues(f.Value))) // case Metadata: // conds = append(conds, fmt.Sprintf("%s = toString('%s')", ColMetadata1, concatValues(f.Value))) // } // } // // add /n to each condition // for i := range conds { // conds[i] += "\n" // } // return conds //} // //func parseOperator(op string) string { // switch strings.ToLower(op) { // case OperatorStringContains: // return OperatorMathEq // interpret as "LIKE" if needed // case OperatorStringIs, OperatorStringOn, "=", OperatorStringOnAny: // return OperatorMathEq // case OperatorStringStartsWith: // // might interpret differently in real impl // return OperatorMathEq // case OperatorStringEndsWith: // // might interpret differently in real impl // return OperatorMathEq // default: // return OperatorMathEq // } //} // //func buildEventsWhere(filters []FilterObj, order EventOrder) (eventConditions []string, having string) { // basicEventTypes := "(" + // strings.Join([]string{ // fmt.Sprintf("%s = 'CLICK'", ColEventName), // fmt.Sprintf("%s = 'INPUT'", ColEventName), // fmt.Sprintf("%s = 'LOCATION'", ColEventName), // fmt.Sprintf("%s = 'CUSTOM'", ColEventName), // fmt.Sprintf("%s = 'REQUEST'", ColEventName), // }, " OR ") + ")" // // var seq []string // for _, f := range filters { // switch f.Type { // case Click: // seq = append(seq, seqCond("CLICK", "selector", f)) // case Input: // seq = append(seq, seqCond("INPUT", "label", f)) // case Location: // seq = append(seq, seqCond("LOCATION", "url_path", f)) // case Custom: // seq = append(seq, seqCond("CUSTOM", "name", f)) // case Fetch: // seq = append(seq, seqFetchCond("REQUEST", f)) // case FetchFilterTypeFetchStatusCode: // seq = append(seq, seqCond("REQUEST", "status", f)) // default: // seq = append(seq, fmt.Sprintf("(%s = '%s')", ColEventName, strings.ToUpper(string(f.Type)))) // } // } // eventConditions = []string{basicEventTypes} // // // then => sequenceMatch // // or => OR // // and => AND // switch order { // case EventOrderThen: // var pattern []string // for i := range seq { // pattern = append(pattern, fmt.Sprintf("(?%d)", i+1)) // } // having = fmt.Sprintf("sequenceMatch('%s')(\n%s,\n%s)", // strings.Join(pattern, ""), fmt.Sprintf("toUnixTimestamp(%s)", ColEventTime), strings.Join(seq, ",\n")) // case EventOrderAnd: // // build AND // having = strings.Join(seq, " AND ") // default: // // default => OR // var orParts []string // for _, p := range seq { // orParts = append(orParts, "("+p+")") // } // having = strings.Join(orParts, " OR ") // } // return //} // //func seqCond(eventName, key string, f FilterObj) string { // op := parseOperator(f.Operator) // return fmt.Sprintf("(%s = '%s' AND JSONExtractString(toString(%s), '%s') %s '%s')", // ColEventName, strings.ToUpper(eventName), ColEventProperties, key, op, concatValues(f.Value)) //} // //func seqFetchCond(eventName string, f FilterObj) string { // w := []string{fmt.Sprintf("(%s = '%s')", ColEventName, strings.ToUpper(eventName))} // var extras []string // for _, c := range f.Filters { // switch c.Type { // case Fetch: // if len(c.Value) > 0 { // extras = append(extras, fmt.Sprintf("(%s = '%s')", ColEventURLPath, concatValues(c.Value))) // } // case FetchFilterTypeFetchStatusCode: // if len(c.Value) > 0 { // extras = append(extras, fmt.Sprintf("(%s = '%s')", ColEventStatus, concatValues(c.Value))) // } // default: // // placeholder if needed // } // } // if len(extras) > 0 { // w = append(w, strings.Join(extras, " AND ")) // } // return "(" + strings.Join(w, " AND ") + ")" //} // //func concatValues(v []string) string { // return strings.Join(v, "") //} //const jsonInput = ` //{ // "startTimestamp": 1737043724664, // "endTimestamp": 1737130124664, // "projectId": 1, // "series": [ // { // "seriesId": 610, // "name": "Series 1", // "filter": { // "filters": [ // { // "type": "click", // "isEvent": true, // "value": ["DEPLOYMENT"], // "operator": "on", // "filters": [] // }, // { // "type": "input", // "isEvent": true, // "value": ["a"], // "operator": "contains", // "filters": [] // }, // { // "type": "location", // "isEvent": true, // "value": ["/en/using-or/"], // "operator": "is", // "filters": [] // }, // { // "type": "userCountry", // "isEvent": false, // "value": ["AD"], // "operator": "is", // "filters": [] // }, // { // "type": "userCity", // "isEvent": false, // "value": ["Mumbai"], // "operator": "is", // "filters": [] // }, // { // "type": "userState", // "isEvent": false, // "value": ["Maharashtra"], // "operator": "is", // "filters": [] // }, // { // "type": "userId", // "isEvent": false, // "value": ["test@test.com"], // "operator": "is", // "filters": [] // }, // { // "type": "userAnonymousId", // "isEvent": false, // "value": ["asd"], // "operator": "is", // "filters": [] // }, // { // "type": "userOs", // "isEvent": false, // "value": ["Mac OS X"], // "operator": "is", // "filters": [] // }, // { // "type": "userBrowser", // "isEvent": false, // "value": ["Chrome"], // "operator": "is", // "filters": [] // }, // { // "type": "userDevice", // "isEvent": false, // "value": ["iPhone"], // "operator": "is", // "filters": [] // }, // { // "type": "platform", // "isEvent": false, // "value": ["desktop"], // "operator": "is", // "filters": [] // }, // { // "type": "revId", // "isEvent": false, // "value": ["v1"], // "operator": "is", // "filters": [] // }, // { // "type": "referrer", // "isEvent": false, // "value": ["https://www.google.com/"], // "operator": "is", // "filters": [] // }, // { // "type": "duration", // "isEvent": false, // "value": ["60000", "6000000"], // "operator": "is", // "filters": [] // }, // { // "type": "utmSource", // "isEvent": false, // "value": ["aaa"], // "operator": "is", // "filters": [] // }, // { // "type": "utmMedium", // "isEvent": false, // "value": ["aa"], // "operator": "is", // "filters": [] // }, // { // "type": "utmCampaign", // "isEvent": false, // "value": ["aaa"], // "operator": "is", // "filters": [] // }, // { // "type": "metadata", // "isEvent": false, // "value": ["bbbb"], // "operator": "is", // "source": "userId", // "filters": [] // }, // { // "type": "custom", // "isEvent": true, // "value": ["test"], // "operator": "is", // "filters": [] // }, // { // "type": "fetch", // "isEvent": true, // "value": [], // "operator": "is", // "filters": [ // { // "type": "fetchUrl", // "isEvent": false, // "value": ["/ai/docs/chat"], // "operator": "is", // "filters": [] // }, // { // "type": "fetchStatusCode", // "isEvent": false, // "value": ["400"], // "operator": "=", // "filters": [] // }, // { // "type": "fetchMethod", // "isEvent": false, // "value": [], // "operator": "is", // "filters": [] // }, // { // "type": "fetchDuration", // "isEvent": false, // "value": [], // "operator": "=", // "filters": [] // }, // { // "type": "fetchRequestBody", // "isEvent": false, // "value": [], // "operator": "is", // "filters": [] // }, // { // "type": "fetchResponseBody", // "isEvent": false, // "value": [], // "operator": "is", // "filters": [] // } // ] // } // ], // "eventsOrder": "then" // } // } // ] //} //`