Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
322 lines (263 sloc) 9.2 KB
/*
* Copyright 2019 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package objectbox
/*
#include <stdlib.h>
#include "objectbox.h"
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"sync"
"unsafe"
)
// A Query allows to search for objects matching user defined conditions.
//
// For example, you can find all people whose last name starts with an 'N':
// box.Query(Person_.LastName.HasPrefix("N", false)).Find()
// Note that Person_ is a struct generated by ObjectBox allowing to conveniently reference properties.
type Query struct {
entity *entity
objectBox *ObjectBox
box *Box
cQuery *C.OBX_query
closeMutex sync.Mutex
offset uint64
limit uint64
linkedEntityIds []TypeId
}
// Frees (native) resources held by this Query.
// Note that this is optional and not required because the GC invokes a finalizer automatically.
func (query *Query) Close() error {
query.closeMutex.Lock()
defer query.closeMutex.Unlock()
if query.cQuery != nil {
return cCall(func() C.obx_err {
var err = C.obx_query_close(query.cQuery)
query.cQuery = nil
return err
})
}
return nil
}
func queryFinalizer(query *Query) {
err := query.Close()
if err != nil {
fmt.Printf("Error while finalizer closed query: %s", err)
}
}
// The native query object in the ObjectBox core is not tied with other resources.
// Thus timing of the Close call is independent from other resources.
func (query *Query) installFinalizer() {
runtime.SetFinalizer(query, queryFinalizer)
}
func (query *Query) errorClosed() error {
return errors.New("illegal state; query was closed")
}
// Find returns all objects matching the query
func (query *Query) Find() (objects interface{}, err error) {
if query.cQuery == nil {
return 0, query.errorClosed()
}
if supportsBytesArray {
var cFn = func() *C.OBX_bytes_array {
return C.obx_query_find(query.cQuery, C.uint64_t(query.offset), C.uint64_t(query.limit))
}
return query.box.readManyObjects(cFn)
} else {
var cFn = func(visitorArg unsafe.Pointer) C.obx_err {
return C.obx_query_visit(query.cQuery, dataVisitor, visitorArg,
C.uint64_t(query.offset), C.uint64_t(query.limit))
}
return query.box.readUsingVisitor(cFn)
}
}
// Offset defines the index of the first object to process (how many objects to skip)
func (query *Query) Offset(offset uint64) *Query {
query.offset = offset
return query
}
// Limit sets the number of elements to process by the query
func (query *Query) Limit(limit uint64) *Query {
query.limit = limit
return query
}
// FindIds returns IDs of all objects matching the query
func (query *Query) FindIds() ([]uint64, error) {
if query.cQuery == nil {
return nil, query.errorClosed()
}
return cGetIds(func() *C.OBX_id_array {
return C.obx_query_find_ids(query.cQuery, C.uint64_t(query.offset), C.uint64_t(query.limit))
})
}
// Count returns the number of objects matching the query
func (query *Query) Count() (uint64, error) {
// doesn't support offset/limit at this point
if query.offset != 0 || query.limit != 0 {
return 0, fmt.Errorf("limit/offset are not supported by Count at this moment")
}
if query.cQuery == nil {
return 0, query.errorClosed()
}
var cResult C.uint64_t
if err := cCall(func() C.obx_err { return C.obx_query_count(query.cQuery, &cResult) }); err != nil {
return 0, err
}
return uint64(cResult), nil
}
// Remove permanently deletes all objects matching the query from the database
func (query *Query) Remove() (count uint64, err error) {
// doesn't support offset/limit at this point
if query.offset != 0 || query.limit != 0 {
return 0, fmt.Errorf("limit/offset are not supported by Remove at this moment")
}
if query.cQuery == nil {
return 0, query.errorClosed()
}
var cResult C.uint64_t
if err := cCall(func() C.obx_err { return C.obx_query_remove(query.cQuery, &cResult) }); err != nil {
return 0, err
}
return uint64(cResult), nil
}
// DescribeParams returns a string representation of the query conditions
func (query *Query) DescribeParams() (string, error) {
if query.cQuery == nil {
return "", query.errorClosed()
}
// no need to free, it's handled by the cQuery internally
cResult := C.obx_query_describe_params(query.cQuery)
return C.GoString(cResult), nil
}
func (query *Query) checkEntityId(entityId TypeId) error {
if query.entity.id == entityId {
return nil
}
if query.linkedEntityIds != nil {
for _, id := range query.linkedEntityIds {
if id == entityId {
return nil
}
}
return fmt.Errorf("property from a different entity %d passed, expected one of %v",
entityId, append([]TypeId{query.entity.id}, query.linkedEntityIds...))
}
return fmt.Errorf("property from a different entity %d passed, expected %d", entityId, query.entity.id)
}
type Property interface {
propertyId() TypeId
entityId() TypeId
}
func (query *Query) SetStringParams(property Property, values ...string) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
} else if len(values) == 1 {
return cCall(func() C.obx_err {
cString := C.CString(values[0])
defer C.free(unsafe.Pointer(cString))
return C.obx_query_string_param(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), cString)
})
}
return fmt.Errorf("too many values given")
}
func (query *Query) SetStringParamsIn(property Property, values ...string) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
}
cStringArray := goStringArrayToC(values)
defer cStringArray.free()
return cCall(func() C.obx_err {
return C.obx_query_string_params_in(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), cStringArray.cArray, C.int(cStringArray.size))
})
}
func (query *Query) SetInt64Params(property Property, values ...int64) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
} else if len(values) == 1 {
return cCall(func() C.obx_err {
return C.obx_query_int_param(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), C.int64_t(values[0]))
})
} else if len(values) == 2 {
return cCall(func() C.obx_err {
return C.obx_query_int_params(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), C.int64_t(values[0]), C.int64_t(values[1]))
})
}
return fmt.Errorf("too many values given")
}
func (query *Query) SetInt64ParamsIn(property Property, values ...int64) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
}
return cCall(func() C.obx_err {
return C.obx_query_int64_params_in(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), (*C.int64_t)(unsafe.Pointer(&values[0])), C.int(len(values)))
})
}
func (query *Query) SetInt32ParamsIn(property Property, values ...int32) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
}
return cCall(func() C.obx_err {
return C.obx_query_int32_params_in(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), (*C.int32_t)(unsafe.Pointer(&values[0])), C.int(len(values)))
})
}
func (query *Query) SetFloat64Params(property Property, values ...float64) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
} else if len(values) == 1 {
return cCall(func() C.obx_err {
return C.obx_query_double_param(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), C.double(values[0]))
})
} else if len(values) == 2 {
return cCall(func() C.obx_err {
return C.obx_query_double_params(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), C.double(values[0]), C.double(values[1]))
})
}
return fmt.Errorf("too many values given")
}
func (query *Query) SetBytesParams(property Property, values ...[]byte) error {
if err := query.checkEntityId(property.entityId()); err != nil {
return err
}
if len(values) == 0 {
return fmt.Errorf("no values given")
} else if len(values) > 1 {
return fmt.Errorf("too many values given")
}
return cCall(func() C.obx_err {
return C.obx_query_bytes_param(query.cQuery, C.obx_schema_id(property.entityId()), C.obx_schema_id(property.propertyId()), cBytesPtr(values[0]), C.size_t(len(values[0])))
})
}
You can’t perform that action at this time.