diff --git a/apier/cdrs.go b/apier/cdre.go similarity index 79% rename from apier/cdrs.go rename to apier/cdre.go index d6483e0c7..c5b77651e 100644 --- a/apier/cdrs.go +++ b/apier/cdre.go @@ -21,6 +21,7 @@ package apier import ( "fmt" "github.com/cgrates/cgrates/cdrexporter" + "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "os" "path" @@ -35,6 +36,12 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E if !utils.IsSliceMember(utils.CdreCdrFormats, cdrFormat) { return fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "CdrFormat") } + exportedFields := self.Config.CdreExportedFields + if len(attr.ExportedFields) != 0 { + if exportedFields, err = config.ParseRSRFields(attr.ExportedFields); err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + } if len(attr.TimeStart) != 0 { if tStart, err = utils.ParseTimeDetectLayout(attr.TimeStart); err != nil { return err @@ -45,7 +52,8 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E return err } } - cdrs, err := self.CdrDb.GetStoredCdrs(tStart, tEnd, attr.SkipErrors, attr.SkipRated) + cdrs, err := self.CdrDb.GetStoredCdrs(attr.MediationRunId, attr.CdrHost, attr.CdrSource, attr.ReqType, attr.Direction, + attr.Tenant, attr.Tor, attr.Account, attr.Subject, attr.DestinationPrefix, tStart, tEnd, attr.SkipErrors, attr.SkipRated) if err != nil { return err } @@ -58,7 +66,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E } else { defer fileOut.Close() } - csvWriter := cdrexporter.NewCsvCdrWriter(fileOut, self.Config.RoundingDecimals, self.Config.CdreExportedFields) + csvWriter := cdrexporter.NewCsvCdrWriter(fileOut, self.Config.RoundingDecimals, exportedFields) for _, cdr := range cdrs { if err := csvWriter.Write(cdr); err != nil { os.Remove(fileName) diff --git a/engine/storage_interface.go b/engine/storage_interface.go index a3a3fa734..dc0b2b0fd 100644 --- a/engine/storage_interface.go +++ b/engine/storage_interface.go @@ -102,7 +102,7 @@ type CdrStorage interface { Storage SetCdr(utils.RawCDR) error SetRatedCdr(*utils.StoredCdr, string) error - GetStoredCdrs(time.Time, time.Time, bool, bool) ([]*utils.StoredCdr, error) + GetStoredCdrs(string, string, string, string, string, string, string, string, string, string, time.Time, time.Time, bool, bool) ([]*utils.StoredCdr, error) RemStoredCdrs([]string) error } diff --git a/engine/storage_sql.go b/engine/storage_sql.go index 19fffe50c..a754c4369 100644 --- a/engine/storage_sql.go +++ b/engine/storage_sql.go @@ -569,10 +569,71 @@ func (self *SQLStorage) SetRatedCdr(storedCdr *utils.StoredCdr, extraInfo string // Return a slice of CDRs from storDb using optional filters.a // ignoreErr - do not consider cdrs with rating errors // ignoreRated - do not consider cdrs which were already rated, including here the ones with errors -func (self *SQLStorage) GetStoredCdrs(timeStart, timeEnd time.Time, ignoreErr, ignoreRated bool) ([]*utils.StoredCdr, error) { +func (self *SQLStorage) GetStoredCdrs(runId, cdrHost, cdrSource, reqType, direction, tenant, tor, account, subject, destPrefix string, + timeStart, timeEnd time.Time, ignoreErr, ignoreRated bool) ([]*utils.StoredCdr, error) { var cdrs []*utils.StoredCdr q := fmt.Sprintf("SELECT %s.cgrid,accid,cdrhost,cdrsource,reqtype,direction,tenant,tor,account,%s.subject,destination,setup_time,answer_time,duration,extra_fields,runid,cost FROM %s LEFT JOIN %s ON %s.cgrid=%s.cgrid LEFT JOIN %s ON %s.cgrid=%s.cgrid", utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_CDRS_PRIMARY, utils.TBL_CDRS_EXTRA, utils.TBL_RATED_CDRS, utils.TBL_CDRS_PRIMARY, utils.TBL_RATED_CDRS) fltr := "" + if len(runId) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" runid='%s'", runId) + } + if len(cdrHost) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" cdrhost='%s'", cdrHost) + } + if len(cdrSource) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" cdrsource='%s'", cdrSource) + } + if len(reqType) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" reqtype='%s'", reqType) + } + if len(direction) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" direction='%s'", direction) + } + if len(tenant) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" tenant='%s'", tenant) + } + if len(tor) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" tor='%s'", tor) + } + if len(account) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" account='%s'", account) + } + if len(subject) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" %s.subject='%s'", utils.TBL_CDRS_PRIMARY, subject) + } + if len(destPrefix) != 0 { + if len(fltr) != 0 { + fltr += " AND " + } + fltr += fmt.Sprintf(" destination LIKE '%s%'", destPrefix) + } if !timeStart.IsZero() { if len(fltr) != 0 { fltr += " AND " diff --git a/mediator/mediator.go b/mediator/mediator.go index b5c3c9591..47781299b 100644 --- a/mediator/mediator.go +++ b/mediator/mediator.go @@ -161,7 +161,7 @@ func (self *Mediator) RateCdr(dbcdr utils.RawCDR) error { } func (self *Mediator) RateCdrs(timeStart, timeEnd time.Time, rerateErrors, rerateRated bool) error { - cdrs, err := self.cdrDb.GetStoredCdrs(timeStart, timeEnd, !rerateErrors, !rerateRated) + cdrs, err := self.cdrDb.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", timeStart, timeEnd, !rerateErrors, !rerateRated) if err != nil { return err } diff --git a/mediator/mediator_local_test.go b/mediator/mediator_local_test.go index 07f43c233..771d75a56 100644 --- a/mediator/mediator_local_test.go +++ b/mediator/mediator_local_test.go @@ -150,12 +150,12 @@ func TestPostCdrs(t *testing.T) { } } time.Sleep(10 * time.Millisecond) // Give time for CDRs to reach database - if storedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 2 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonErrorCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, true, false); err != nil { + if nonErrorCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, false); err != nil { t.Error(err) } else if len(nonErrorCdrs) != 0 { // Just two of them should be without errors t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(nonErrorCdrs))) @@ -178,12 +178,12 @@ func TestInjectCdrs(t *testing.T) { t.Error(err) } } - if storedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, false, false); err != nil { + if storedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, false); err != nil { t.Error(err) } else if len(storedCdrs) != 4 { // Make sure CDRs made it into StorDb t.Error(fmt.Sprintf("Unexpected number of CDRs stored: %d", len(storedCdrs))) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, true, true); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 2 { // Just two of them should be non-rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) @@ -215,12 +215,12 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if nonRatedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, true, true); err != nil { + if nonRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, true, true); err != nil { t.Error(err) } else if len(nonRatedCdrs) != 0 { // Just two of them should be non-rated t.Error(fmt.Sprintf("Unexpected number of CDRs non-rated: %d", len(nonRatedCdrs))) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, false, true); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { t.Error(err) } else if len(errRatedCdrs) != 2 { // The first 2 with errors should be still there before rerating t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) @@ -230,7 +230,7 @@ func TestRateCdrs(t *testing.T) { } else if reply != utils.OK { t.Errorf("Unexpected reply: %s", reply) } - if errRatedCdrs, err := cdrStor.GetStoredCdrs(time.Time{}, time.Time{}, false, true); err != nil { + if errRatedCdrs, err := cdrStor.GetStoredCdrs("", "", "", "", "", "", "", "", "", "", time.Time{}, time.Time{}, false, true); err != nil { t.Error(err) } else if len(errRatedCdrs) != 1 { // One CDR with errors should be fixed now by rerating t.Error(fmt.Sprintf("Unexpected number of CDRs with errors: %d", len(errRatedCdrs))) diff --git a/utils/apitpdata.go b/utils/apitpdata.go index 7b74d6cee..17f80bfe6 100644 --- a/utils/apitpdata.go +++ b/utils/apitpdata.go @@ -309,12 +309,23 @@ type CachedItemAge struct { } type AttrExpFileCdrs struct { - CdrFormat string // Cdr output file format - TimeStart string // If provided, will represent the starting of the CDRs interval (>=) - TimeEnd string // If provided, will represent the end of the CDRs interval (<) - SkipErrors bool // Do not export errored CDRs - SkipRated bool // Do not export rated CDRs - RemoveFromDb bool // If true the CDRs will be also deleted after export + CdrFormat string // Cdr output file format + ExportedFields string // Optional comma separated list of fields ot be exported in a CDR + MediationRunId string // If provided, it will filter on mediation runid + CdrHost string // If provided, it will filter cdrhost + CdrSource string // If provided, it will filter cdrsource + ReqType string // If provided, it will fiter reqtype + Direction string // If provided, it will fiter direction + Tenant string // If provided, it will filter tenant + Tor string // If provided, it will filter tor + Account string // If provided, it will filter account + Subject string // If provided, it will filter the rating subject + DestinationPrefix string // If provided, it will filter on destination prefix + TimeStart string // If provided, it will represent the starting of the CDRs interval (>=) + TimeEnd string // If provided, it will represent the end of the CDRs interval (<) + SkipErrors bool // Do not export errored CDRs + SkipRated bool // Do not export rated CDRs + RemoveFromDb bool // If true the CDRs will be also deleted after export } type ExportedFileCdrs struct {