
Reindexer هي قاعدة بيانات قابلة للتضمين ، في الذاكرة ، موجهة نحو المستندات مع واجهة منشئ استعلام عالية المستوى.
هدف Rindexer هو توفير البحث السريع مع استفسارات معقدة. لم نكن سعداء بـ Elasticsearch وأنشأنا Reindexer كبديل أكثر أداء.
يتم كتابة النواة في C ++ و API مستوى التطبيق قيد التنفيذ.
يصف هذا المستند موصل Go و API الخاص به. للحصول على معلومات حول خادم Reindexer و HTTP API ، يرجى الرجوع إلى وثائق ReindExer
هناك نوعان من LTS من Rindexer المتاحة: V3.xx و V4.xx
3.xx هو حاليًا فرعنا الرئيسي و 4.xx (فرع الإصدار/4) هو الإصدار التجريبي مع الدعم التجريبي ودعم Sharding. تتماشى المخازن بين تلك الإصدارات ، ومع ذلك ، فإن تكوينات النسخ المتماثل مختلفة تمامًا. يحصلان الإصدارات 3 و 4 على نفس الإثارات والميزات (باستثناء تلك المتعلقة بالنسخ المتماثل).
الميزات الرئيسية:
لقد كان الأداء أولويتنا القصوى منذ البداية ، ونعتقد أننا تمكنا من الحصول عليه جيدًا. تظهر المعايير أن أداء Reindexer على قدم المساواة مع قاعدة بيانات قيمة مفتاح نموذجية. على قلب وحدة المعالجة المركزية واحدة ، نحصل على:
SELECT * FROM items WHERE id='?'SELECT * FROM items WHERE year > 2010 AND name = 'string' AND id IN (....)SELECT * FROM items WHERE year > 2010 AND name = 'string' JOIN subitems ON ...انظر نتائج القياس والمزيد من التفاصيل في القياس الريبو
يهدف Reindexer إلى استهلاك قدر الإمكان الذاكرة ؛ تتم معالجة معظم الاستعلامات دون تخصيص الذاكرة على الإطلاق.
لتحقيق ذلك ، يتم استخدام العديد من التحسينات ، سواء على مستوى C ++ ومستوى GO:
يتم تخزين المستندات والمؤشرات في هياكل C ++ ثنائية كثيفة ، بحيث لا تفرض أي عبء على جامع GO's Garbage.
يتم دمج التكرارات السلسلة.
تبلغ نسبة النفقات العامة للذاكرة حوالي 32 بايت لكل مستند + بايت 4-16 بايت لكل فهرس بحث.
هناك ذاكرة التخزين المؤقت للكائن على مستوى GO للمستندات التي يتم إلغاؤها المنتج بعد تنفيذ الاستعلام. تستخدم الاستعلامات المستقبلية مستندات مسبقة ما قبل العرض ، والتي تقلل من تكاليف التخصيص والتخصيص المتكرر
تستخدم واجهة الاستعلام sync.Pool لإعادة استخدام الهياكل الداخلية والمخازن المؤقتة. يتيح مزيج من هذه التقنيات المُردة التعامل مع معظم الاستعلامات دون أي تخصيصات.
يتمتع Rindexer بمحرك بحث داخلي كامل للبحث عن النص. مستندات استخدام البحث الكامل وأمثلة هنا
يمكن لـ Rindexer تخزين المستندات وتحميل المستندات من القرص عبر LevelDB. تتم كتابة المستندات إلى الواجهة الخلفية للتخزين بشكل غير متزامن بواسطة دفعات كبيرة تلقائيًا في الخلفية.
عند إنشاء مساحة الاسم ، يتم تخزين جميع مستنداتها في ذاكرة الوصول العشوائي ، وبالتالي فإن الاستعلامات في هذه المستندات تعمل بالكامل في الوضع في الذاكرة.
فيما يلي مثال كامل على استخدام المُردة الأساسي:
package main
// Import package
import (
"fmt"
"math/rand"
"github.com/restream/reindexer/v3"
// choose how the Reindexer binds to the app (in this case "builtin," which means link Reindexer as a static library)
_ "github.com/restream/reindexer/v3/bindings/builtin"
// OR use Reindexer as standalone server and connect to it via TCP or unix domain socket (if available).
// _ "github.com/restream/reindexer/v3/bindings/cproto"
// OR link Reindexer as static library with bundled server.
// _ "github.com/restream/reindexer/v3/bindings/builtinserver"
// "github.com/restream/reindexer/v3/bindings/builtinserver/config"
)
// Define struct with reindex tags. Fields must be exported - private fields can not be written into reindexer
type Item struct {
ID int64 `reindex:"id,,pk"` // 'id' is primary key
Name string `reindex:"name"` // add index by 'name' field
Articles [] int `reindex:"articles"` // add index by articles 'articles' array
Year int `reindex:"year,tree"` // add sortable index by 'year' field
}
func main () {
// Init a database instance and choose the binding (builtin)
db := reindexer . NewReindex ( "builtin:///tmp/reindex/testdb" )
// OR - Init a database instance and choose the binding (connect to server via TCP sockets)
// Database should be created explicitly via reindexer_tool or via WithCreateDBIfMissing option:
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("cproto://user:[email protected]:6534/testdb", reindexer.WithCreateDBIfMissing())
// OR - Init a database instance and choose the binding (connect to server via unix domain sockets)
// Unix domain sockets are available on the unix systems only (socket file has to be explicitly set on the server's side with '--urpcaddr' option)
// Database should be created explicitly via reindexer_tool or via WithCreateDBIfMissing option:
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("ucproto://user:pass@/tmp/reindexer.socket:/testdb", reindexer.WithCreateDBIfMissing())
// OR - Init a database instance and choose the binding (builtin, with bundled server)
// serverConfig := config.DefaultServerConfig ()
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("builtinserver://user:pass@testdb",reindexer.WithServerConfig(100*time.Second, serverConfig))
// Create new namespace with name 'items', which will store structs of type 'Item'
db . OpenNamespace ( "items" , reindexer . DefaultNamespaceOptions (), Item {})
// Generate dataset
for i := 0 ; i < 100000 ; i ++ {
err := db . Upsert ( "items" , & Item {
ID : int64 ( i ),
Name : "Vasya" ,
Articles : [] int { rand . Int () % 100 , rand . Int () % 100 },
Year : 2000 + rand . Int () % 50 ,
})
if err != nil {
panic ( err )
}
}
// Query a single document
elem , found := db . Query ( "items" ).
Where ( "id" , reindexer . EQ , 40 ).
Get ()
if found {
item := elem .( * Item )
fmt . Println ( "Found document:" , * item )
}
// Query multiple documents
query := db . Query ( "items" ).
Sort ( "year" , false ). // Sort results by 'year' field in ascending order
WhereString ( "name" , reindexer . EQ , "Vasya" ). // 'name' must be 'Vasya'
WhereInt ( "year" , reindexer . GT , 2020 ). // 'year' must be greater than 2020
WhereInt ( "articles" , reindexer . SET , 6 , 1 , 8 ). // 'articles' must contain one of [6,1,8]
Limit ( 10 ). // Return maximum 10 documents
Offset ( 0 ). // from 0 position
ReqTotal () // Calculate the total count of matching documents
// Execute the query and return an iterator
iterator := query . Exec ()
// Iterator must be closed
defer iterator . Close ()
fmt . Println ( "Found" , iterator . TotalCount (), "total documents, first" , iterator . Count (), "documents:" )
// Iterate over results
for iterator . Next () {
// Get the next document and cast it to a pointer
elem := iterator . Object ().( * Item )
fmt . Println ( * elem )
}
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
}هناك أيضًا بعض العينات الأساسية لـ C ++ واذهب هنا
كبديل عن Query Builder Reindexer يوفر واجهة استعلام متوافقة مع SQL. فيما يلي عينة من استخدام واجهة SQL:
...
iterator := db . ExecSQL ( "SELECT * FROM items WHERE name='Vasya' AND year > 2020 AND articles IN (6,1,8) ORDER BY year LIMIT 10" )
...يرجى ملاحظة أن واجهة باني الاستعلام مفضلة: إنها تحتوي على ميزات أكبر وأسرع من واجهة SQL
يجب أن تكون حرفية سلسلة في اقتباسات واحدة.
يجب أن تكون فهارس مركبة محاطة في عروض أسعار مزدوجة.
SELECT * FROM items WHERE " field1+field2 " = ' Vasya 'إذا لم يبدأ اسم الحقل بـ alpha أو '_' أو '#' فيجب أن يتم إرفاقها في عروض أسعار مزدوجة ، أمثلة:
UPDATE items DROP " 123 " SELECT * FROM ns WHERE " 123 " = ' some_value ' SELECT * FROM ns WHERE " 123abc " = 123 DELETE FROM ns WHERE " 123abc123 " = 111قد يتم تنفيذ صلة بسيطة عبر بناء جملة SQL الافتراضي:
SELECT * FROM ns INNER JOIN ns2 ON ns2 . id = ns . fk_id WHERE a > 0يجب أن تستخدم CONFE مع حالة على مساحة الاسم اليسرى بناء جملة تشبه الاستعامة الفرعية:
SELECT * FROM ns WHERE a > 0 AND INNER JOIN ( SELECT * FROM ns2 WHERE b > 10 AND c = 1 ) ON ns2 . id = ns . fk_idيمكن أن يكون الانبعاث الفرعي أيضًا جزءًا من الشرط:
SELECT * FROM ns WHERE ( SELECT * FROM ns2 WHERE id < 10 LIMIT 0 ) IS NOT NULL SELECT * FROM ns WHERE id = ( SELECT id FROM ns2 WHERE id < 10 ) SELECT * FROM ns WHERE ( SELECT COUNT ( * ) FROM ns2 WHERE id < 10 ) > 18 يمكن أن يعمل Reindexer في 3 أوضاع مختلفة:
embedded (builtin) في التطبيق كمكتبة ثابتة ، ولا يتطلب عملية خادم منفصلة.embedded with server (builtinserver) يتم تضمين Reindexer في التطبيق كمكتبة ثابتة ، وبدء الخادم. في هذا الوضع ، يمكن للعملاء الآخرين الاتصال بالتطبيق عبر CPROTO أو UCPROTO أو HTTP.standalone كخادم مستقل ، يتصل التطبيق بـ Reindexer عبر مآخذ النطاق أو UNIX.في هذا الوضع ، لا يعتمد ملزمة GODEXER على المكتبة الثابتة لـ Reindexer.
إن أبسط طريقة للحصول على خادم Reindexer ، هي سحب وتشغيل صورة Docker من DockerHub.
docker run -p9088:9088 -p6534:6534 -it reindexer/reindexerDockerfile
تتم كتابة جوهر ReindExer في C ++ 17 ويستخدم LevelDB كواجهة خلفية للتخزين ، لذلك يجب تثبيت CMAKE و C ++ 17 Toolchain و LevelDB قبل تثبيت Reindexer.
لبناء ReindExer ، مطلوب G ++ 8+ أو Clang 7+ أو Mingw64.
في تلك الأوضاع ، يعتمد ربط الرابطة على المكتبات الثابتة لـ Reindexer (الأساسية والخادم والموارد).
يوصى بهذه الطريقة وسوف يناسب معظم السيناريوهات.
GO MODULES مع GO.MOD لا تسمح ببناء مكتبات C ++ في أدلة الوحدات النمطية. سيستخدم الارتباط GO-CONFIG للكشف عن أدلة المكتبات.
يجب تثبيت مكتبات Rindexer إما من مصادر أو من الحزمة المسبقة عبر Package Manager.
ثم احصل على الوحدة:
go get -a github.com/restream/reindexer/v3 إذا كنت بحاجة إلى مصادر Rindexer المعدلة ، فيمكنك استخدام replace مثل هذا.
# Clone reindexer via git. It's also possible to use 'go get -a github.com/restream/reindexer/v3', but it's behavior may vary depending on Go's version
git clone https://github.com/restream/reindexer.git $GOPATH /src/reindexer
bash $GOPATH /src/reindexer/dependencies.sh
# Generate builtin binding
cd $GOPATH /src/reindexer
go generate ./bindings/builtin
# Optional (build builtin server binding)
go generate ./bindings/builtinserver # Go to your app's directory
cd /your/app/path
go get -a github.com/restream/reindexer/v3
go mod edit -replace github.com/restream/reindexer/v3= $GOPATH /src/reindexerفي هذه الحالة ، ستقوم ربط GO- بإنشاء مكتبات ومسارات واضحة ولن تستخدم PKG-Config.
إذا كنت لا تستخدم go.mod ، فمن الممكن الحصول على مجموعة من المصادر بهذه الطريقة:
export GO111MODULE=off # Disable go1.11 modules
# Go to your app's directory
cd /your/app/path
# Clone reindexer via git. It's also possible to use 'go get -a github.com/restream/reindexer', but it's behavior may vary depending on Go's version
git clone --branch master https://github.com/restream/reindexer.git vendor/github.com/restream/reindexer/v3
# Generate builtin binding
go generate -x ./vendor/github.com/restream/reindexer/v3/bindings/builtin
# Optional (build builtin server binding)
go generate -x ./vendor/github.com/restream/reindexer/v3/bindings/builtinserverلا يدعم GO البلاط المناسب لرمز CGO (Golang/GO#26366) ، ومع ذلك ، من الممكن استخدام Pend لنسخ مصادر Reindexer في دليل البائع.
مع vend ، ستتمكن من الاتصال go generate -mod=vendor لـ builtin و builtinserver ، وضعت في دليل البائع الخاص بك.
من الممكن أيضًا نسخ ببساطة نسخ مصادر Reindexer إلى مشروع الشباب ، باستخدام git clone .
في هذه الحالات ، يجب تثبيت جميع التبعيات الناتجة عن GO.MOD يدويًا مع الإصدارات المناسبة.
داخليًا ، يتم تقسيم الهياكل إلى جزأين:
reindex الاستعلامات ممكنة فقط على الحقول المفهرسة ، والتي تحمل علامة reindex . تحتوي علامة reindex على اسم الفهرس ، والكتابة ، والخيارات الإضافية:
reindex:"<name>[[,<type>],<opts>]"
name - اسم الفهرس.type - نوع الفهرس:hash - الاختيار السريع بواسطة Eq وضبط المباراة. تستخدم بشكل افتراضي. يسمح الفرز البطيء وغير الفعال حسب الحقل.tree - Fast Select by Range و GT و LT Matches. أبطأ قليلاً بالنسبة لل EQ ومطابقات مجموعة من فهرس hash . يسمح نتائج الفرز السريع حسب الحقل.text - فهرس البحث النص الكامل. تم وصف تفاصيل الاستخدام للبحث عن النص الكامل هنا- - فهرس العمود. لا يمكن تنفيذ التحديد السريع لأنه يتم تنفيذه باستخدام تقنية كاملة المسح. لديه أصغر الذاكرة النفقات العامة.ttl - TTL الذي يعمل فقط مع حقول INT64. هذه الفهارس مريحة تمامًا لتمثيل حقول التاريخ (المخزنة كـ UNIX Timestamps) التي تنتهي بعد مبلغ محدد من الثواني.rtree - متاح فقط Dwithin مباراة. مقبول فقط لنوع الحقل [2]float64 (أو reindexer.Point ). لمزيد من التفاصيل ، انظر القسم الفرعي للهندسة.opts - خيارات فهرس إضافية:pk - الحقل جزء من المفتاح الأساسي. يجب أن يكون للبنيت حقل واحد على الأقل موسومة مع pkcomposite - إنشاء فهرس مركب. يجب أن يكون نوع الحقل عبارة عن بنية فارغة: struct{} .joined - الحقل هو مستلم للانضمام. يجب أن يكون نوع الحقل []*SubitemType .dense - تقليل حجم الفهرس. بالنسبة إلى hash tree ، سيوفر 8 بايت لكل قيمة مفتاح فريدة. ل - سيوفر 4-8 بايت لكل عنصر. مفيد للفهارس ذات الانتقائية العالية ، ولكن بالنسبة لفهارس tree hash ذات الانتقائية المنخفضة يمكن أن يقلل بشكل خطير من أداء التحديث. سوف يتباطأ dense أيضًا استفسارات كاملة في فهرس - فهارس ، بسبب نقص تحسين ذاكرة التخزين المؤقت للوحدة المعالجة المركزية.sparse - ROW (المستند) على قيمة فهرس متناثر فقط في حالة تعيينه عن قصد - لا توجد سجلات فارغة (أو افتراضية) لهذا النوع من الفهارس في الصف (المستند). يسمح بإنقاذ ذاكرة الوصول العشوائي ، لكنه سيكلفك الأداء - فهو يعمل أبطأ قليلاً من الفهارس العادية.collate_numeric - إنشاء فهرس السلسلة الذي يوفر ترتيب القيم في تسلسل رقمي. يجب أن يكون نوع الحقل سلسلة.collate_ascii - إنشاء مؤشر سلسلة غير حساسة للحالة يعمل مع ASCII. يجب أن يكون نوع الحقل سلسلة.collate_utf8 - إنشاء فهرس سلسلة غير حساسة للحالة مع UTF8. يجب أن يكون نوع الحقل سلسلة.collate_custom=<ORDER> - إنشاء فهرس سلسلة الطلب المخصصة. يجب أن يكون نوع الحقل سلسلة. <ORDER> هو تسلسل الحروف ، والتي تحدد ترتيب الفرز.linear ، quadratic ، greene أو rstar - حدد خوارزمية لبناء مؤشر rtree (بشكل افتراضي rstar ). لمزيد من التفاصيل ، انظر القسم الفرعي للهندسة.uuid - قم بتخزين هذه القيمة كـ uuid. هذا أكثر فعالية بكثير من وجهة نظر إكتمت RAM/الشبكة لـ UUIDs ، من الأوتار. يتم دعم أنواع hash و - الفهرس فقط لـ UUIDs. يمكن استخدامه مع أي متغير UUID ، باستثناء البديل 0 الحقول ذات الفهارس العادية ليست قابلة للإلغاء. يتم دعم الحالة is NULL فقط من خلال فهارس sparse array .
بشكل افتراضي ، يقوم Rindexer بمسح جميع الهياكل المتداخلة ويضيف حقولها إلى مساحة الاسم (وكذلك الفهارس المحددة). أثناء الفهرس ، قم بالمسح الضوئي الخاص (الحقول غير المألوفة) ، وسيتم تخطي الحقول الموسومة بـ reindex:"-" والحقول الموسومة بـ json:"-" .
type Actor struct {
Name string `reindex:"actor_name"`
Age int `reindex:"age"`
}
type BaseItem struct {
ID int64 `reindex:"id,hash,pk"`
UUIDValue string `reindex:"uuid_value,hash,uuid"`
}
type ComplexItem struct {
BaseItem // Index fields of BaseItem will be added to reindex
Actor [] Actor // Index fields ("name" and "age") of Actor will be added to reindex as array-indexes
Name string `reindex:"name"` // Hash-index for "name"
Year int `reindex:"year,tree"` // Tree-index for "year"
Value int `reindex:"value,-"` // Store(column)-index for "value"
Metainfo int `json:"-"` // Field "MetaInfo" will not be stored in reindexer
Parent * Item `reindex:"-"` // Index fields of "Parent" will NOT be added to reindex and all of the "Parent" exported content will be stored as non-indexed data
ParentHidden * Item `json:"-"` // Indexes and fields of "ParentHidden" will NOT be added to reindexer
privateParent * Item // Indexes and fields of "ParentHidden" will NOT be added to reindexer (same as with `json:"-"`)
AnotherActor Actor `reindex:"actor"` // Index fields of "AnotherActor" will be added to reindexer with prefix "actor." (in this example two indexes will be created: "actor.actor_name" and "actor.age")
}يمكن لـ Rindexer فرز المستندات حسب الحقول (بما في ذلك الحقول المتداخلة وحقول مساحات الأسماء المرتبطة) أو عن طريق التعبيرات في ترتيب تصاعدي أو تنازلي.
لفرز الحقول غير المؤشر ، يجب أن تكون جميع القيم قابلة للتحويل لبعضها البعض ، أي إما أن لها نفس الأنواع أو تكون واحدة من الأنواع الرقمية ( bool ، int ، int64 أو float ).
يمكن أن تحتوي التعبيرات على:
bool أو int أو int64 أو float أو string . يجب أن تكون جميع القيم قابلة للتحويل إلى أرقام تتجاهل المساحات الرائدة والتشطيب ؛rank() ، abs() و ST_Distance() ؛+ ، - (أحادي وثنائي) ، * و / . إذا كان اسم الحقل متبوعًا بـ + يجب فصله عن طريق الفضاء لتمييز اسم الفهرس المركب. يجب كتابة حقول مساحات الأسماء المرتبطة على هذا النحو: joined_namespace.field .
Abs() تعني القيمة المطلقة للحجة.
Rank() تعني رتبة النص الكامل للمطابقة ويطبق فقط في الاستعلام الكامل.
ST_Distance() يعني المسافة بين نقاط الهندسة (انظر القسم الفرعي الهندسي). يمكن أن تكون النقاط أعمدة في مساحات الأسماء الحالية أو المرتبطة أو النقطة الثابتة في التنسيق ST_GeomFromText('point(1 -3)')
في SQL استعلام يجب نقل التعبير عن التعبير.
type Person struct {
Name string `reindex:"name"`
Age int `reindex:"age"`
}
type City struct {
Id int `reindex:"id"`
NumberOfPopulation int `reindex:"population"`
Center reindexer. Point `reindex:"center,rtree,linear"`
}
type Actor struct {
ID int `reindex:"id"`
PersonData Person `reindex:"person"`
Price int `reindex:"price"`
Description string `reindex:"description,text"`
BirthPlace int `reindex:"birth_place_id"`
Location reindexer. Point `reindex:"location,rtree,greene"`
}
... .
query := db . Query ( "actors" ). Sort ( "id" , true ) // Sort by field
... .
query = db . Query ( "actors" ). Sort ( "person.age" , true ) // Sort by nested field
... .
// Sort by joined field
// Works for inner join only, when each item from left namespace has exactly one joined item from right namespace
query = db . Query ( "actors" ).
InnerJoin ( db . Query ( "cities" )). On ( "birth_place_id" , reindexer . EQ , "id" ).
Sort ( "cities.population" , true )
... .
// Sort by expression:
query = db . Query ( "actors" ). Sort ( "person.age / -10 + price / 1000 * (id - 5)" , true )
... .
query = db . Query ( "actors" ). Where ( "description" , reindexer . EQ , "ququ" ).
Sort ( "rank() + id / 100" , true ) // Sort with fulltext rank
... .
// Sort by geometry distance
query = db . Query ( "actors" ).
Join ( db . Query ( "cities" )). On ( "birth_place_id" , reindexer . EQ , "id" ).
SortStPointDistance ( cities . center , reindexer. Point { 1.0 , - 3.0 }, true ).
SortStFieldDistance ( "location" , "cities.center" , true )
... .
// In SQL query:
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY person.name ASC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors WHERE description = 'ququ' ORDER BY 'rank() + id / 100' DESC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY 'ST_Distance(location, ST_GeomFromText( ' point(1 -3) ' ))' ASC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY 'ST_Distance(location, cities.center)' ASC" )من الممكن أيضًا تعيين ترتيب فرز مخصص مثل هذا
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=a-zA-Z0-9"`
}أو مثل هذا
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭ-ЯAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0-9ЁёЙйэ-я"`
}الشخصية الأولى في هذه القائمة لها أولوية أعلى ، أولوية الشخصية الأخيرة هي أصغر. وهذا يعني أن خوارزمية الفرز ستضع العناصر التي تبدأ مع الشخصية الأولى قبل الآخرين. إذا تم تخطي بعض الشخصيات ، فسيكون لها قيمها المعتادة (وفقًا للأحرف الواردة في القائمة).
للحصول على نمط نص البحث البسيط في حالة حقول السلسلة ، يمكن استخدام LIKE . يبحث عن سلاسل تطابق النمط. في النمط _ يعني أي char و % يعني أي تسلسل من chars.
انطلق على سبيل المثال:
query := db . Query ( "items" ).
Where ( "field" , reindexer . LIKE , "pattern" )مثال SQL:
SELECT * FROM items WHERE fields LIKE ' pattern 'يتوافق "ME_T" مع "Meet" و "Meat" و "Melt" وهكذا على "٪ tion" يتوافق مع "نشوئها" و "الشرط" و "الخلق" وما إلى ذلك
تحذير : حالة مثل استخدام طريقة المسح. يمكن استخدامه لأغراض التصحيح أو داخل الاستعلامات ذات الظروف الانتقائية الجيدة الأخرى.
بشكل عام للبحث عن النص الكامل بسرعة معقولة ، نوصي باستخدام فهرس النص الكامل.
يتم استخدام استعلامات التحديث لتعديل العناصر الموجودة في مساحة الاسم. هناك عدة أنواع من استفسارات التحديث: تحديث الحقول الحالية ، وإضافة حقول جديدة وإسقاط الحقول غير المحتملة الحالية.
تحديث SQL-Syntax
UPDATE nsName
SET field1 = value1, field2 = value2, ..
WHERE condition;من الممكن أيضًا استخدام التعبيرات الحسابية مع +، -، /، * و Brackets
UPDATE NS SET field1 = field2 + field3 - (field4 + 5 ) / 2 بما في ذلك وظائف مثل now() و sec() و serial() . لاستخدام التعبيرات من طريقة SetExpression() يجب استدعاء طريقة Set () بدلاً من Set() .
لجعل حقل صفيف فارغ
UPDATE NS SET arrayfield = [] WHERE id = 100وتعيينه على Null
UPDATE NS SET field = NULL WHERE id > 100في حالة الحقول غير المخصصة ، فإن ضبط قيمته على قيمة نوع مختلف سيحل محلها تمامًا ؛ في حالة الحقول المفهرسة ، من الممكن فقط تحويلها من النوع المجاور (الأنواع المتكاملة والطعام) ، والسلاسل الرقمية (مثل "123456") إلى أنواع متكاملة والعودة. تعيين الحقل المفهرس لإعادة تعيينه إلى قيمة افتراضية.
من الممكن إضافة حقول جديدة إلى العناصر الموجودة
UPDATE NS SET newField = ' Brand new! ' WHERE id > 100وحتى إضافة حقل جديد بواسطة مسار متداخل معقد مثل هذا
UPDATE NS SET nested . nested2 . nested3 . nested4 .newField = ' new nested field! ' WHERE id > 100سيقوم بإنشاء الأشياء المتداخلة التالية: متداخلة ، متداخلة 2 ، nested3 ، nested4 و Newfield كعضو في Object Nested4.
مثال على استخدام استفسارات التحديث في رمز Golang:
db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Set ( "field1" , values ). Update ()يتيح Tenindexer تحديث حقول الكائن وإضافة. يمكن تعيين الكائن إما بواسطة بنية أو خريطة أو صفيف بايت (وهو إصدار JSON من تمثيل الكائن).
type ClientData struct {
Name string `reindex:"name" json:"name"`
Age int `reindex:"age" json:"age"`
Address int `reindex:"year" json:"year"`
Occupation string `reindex:"occupation" json:"occupation"`
TaxYear int `reindex:tax_year json:"tax_year"`
TaxConsultant string `reindex:tax_consultant json:"tax_consultant"`
}
type Client struct {
ID int `reindex:"id" json:"id"`
Data ClientData `reindex:"client_data" json:"client_data"`
...
}
clientData := updateClientData ( clientId )
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "client_data" , clientData ). Update () في هذه الحالة ، يمكن أن تعمل Map في Golang فقط مع String كمفتاح. map[string]interface{} هي خيار مثالي.
تحديث حقل الكائن بواسطة بيان SQL:
UPDATE clients SET client_data = { " Name " : " John Doe " , " Age " : 40 , " Address " : " Fifth Avenue, Manhattan " , " Occupation " : " Bank Manager " , " TaxYear " : 1999 , " TaxConsultant " : " Jane Smith " } WHERE id = 100 ;تحديث SQL-Syntax من الاستعلامات التي تسقط الحقول غير المقيدة الحالية:
UPDATE nsName
DROP field1, field2, ..
WHERE condition; db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Drop ( "field1" ). Update ()تتيح آلية تحديث Rindexer تعديل حقول الصفيف: لتعديل عنصر معين من صفيف موجود أو حتى لاستبدال حقل كامل.
لتحديث بناء جملة مشغل اشتراك العنصر:
UPDATE NS SET array[ * ].prices[ 0 ] = 9999 WHERE id = 5 حيث * يعني جميع العناصر.
لتحديث مجموعة كاملة ، يتم استخدام ما يلي:
UPDATE NS SET prices = [ 999 , 1999 , 2999 ] WHERE id = 9يمكن تحويل أي حقل غير مفهرس بسهولة إلى صفيف باستخدام هذا الجملة.
يسمح Reindexer أيضًا بتحديث عناصر صفائف الكائنات:
UPDATE NS SET extra . objects [ 0 ] = { " Id " : 0 , " Description " : " Updated! " } WHERE id = 9أيضا مثل هذا
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "extra.objects[0]" , updatedValue ). Update ()يدعم Reindexer المصفوفات غير المتجانسة:
UPDATE NS SET info = [ " hi " , " bro " , 111 , 2 . 71 ] WHERE id = 9 q := DB . Query ( ns ). Where ( "id" , reindexer . EQ , 1 ). Set ( "array" , [] interface {}{ "whatsup" , 777 , "bro" })
res , err := q . Update (). FetchAll ()تدعم حقول صفيف الفهرس القيم التي يمكن تحويلها إلى نوع فهرس فقط. عند حفظها ، قد تتغير هذه القيم عن الدقة بسبب التحويل.
UPDATE NS SET prices_idx = [ 11 , ' 2 ' , 3 ]لإزالة العنصر عن طريق الفهرس ، يجب عليك القيام بما يلي:
UPDATE NS DROP array[ 5 ]لإضافة عناصر إلى صفيف موجود ، يتم دعم بناء الجملة التالي:
UPDATE NS SET integer_array = integer_array || [ 5 , 6 , 7 , 8 ]و
UPDATE NS SET integer_array = [ 1 , 2 , 3 , 4 , 5 ] || integer_array أول واحد يضيف عناصر إلى نهاية integer_array ، وتضيف العناصر الثانية 5 عناصر إلى مقدمة ذلك. لجعل هذا الرمز يعمل في Golang SetExpression() يجب استخدامه بدلاً من Set() .
لإزالة العناصر بالقيمة في صفيف موجود ، يتم دعم بناء الجملة التالي:
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 , 6 , 7 , 8 ])و
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 , 6 ]) أول واحد يزيل جميع حوادث القيم المدرجة في integer_array ، والثاني يحذف فقط الحدوث الأول الموجود. لجعل هذا الرمز يعمل في Golang SetExpression() يجب استخدامه بدلاً من Set() . إذا كنت بحاجة إلى إزالة قيمة واحدة ، فيمكنك استخدام قوسين مربع [5] أو قيمة بسيطة 5 .
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 ]) update ns set integer_array = array_remove(integer_array, 5 )يمكن دمج الإزالة مع Concatenate المصفوفة:
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 ]) || [ 1 , 2 , 3 ]أيضا مثل هذا
db . Query ( "main_ns" ). SetExpression ( "integer_array" , "array_remove(integer_array, [5,6,7,8]) || [1,2,3]" ). Update ()من الممكن إزالة قيم الحقل الثاني من قيم الحقل الأول. وأضف أيضًا قيمًا جديدة وما إلى ذلك. ملاحظة: من المتوقع أن تكون المعلمة الأولى في الأوامر عبارة عن صفيف/عائق حقل ، يمكن أن تكون المعلمة الثانية عبارة عن صفيف/عهد حقل/حقل/حقل. لتوافق القيم/القابلية للتحويل المطلوبة
UPDATE NS SET integer_array = [ 3 ] || array_remove(integer_array, integer_array2) || integer_array3 || array_remove_once(integer_array, [ 8 , 1 ]) || [ 2 , 4 ] UPDATE NS SET integer_array = array_remove(integer_array, integer_array2) || array_remove(integer_array, integer_array3) || array_remove_once(integer_array, [ 33 , 777 ]) db . Query ( "main_ns" ). SetExpression ( "integer_array" , "[3] || array_remove(integer_array, integer_array2) || integer_array3 || array_remove(integer_array, [8,1]) || [2,4]" ). Update () يدعم Reindexer المعاملات. المعاملة هي تحديث مساحة الاسم الذرية. هناك معاملة متزامنة وغير متزامنة متوفرة. لبدء طريقة المعاملات db.BeginTx() يتم استخدامها. تقوم هذه الطريقة بإنشاء كائن معاملة ، والذي يوفر واجهة تحديث/upsert/insert/deert المعتادة للتطبيق. بالنسبة لعملاء RPC ، يوجد قيود على عدد المعاملات - لا يمكن أن يكون لكل اتصال أكثر من 1024 معاملات مفتوحة في نفس الوقت.
// Create new transaction object
tx , err := db . BeginTx ( "items" );
if err != nil {
panic ( err )
}
// Fill transaction object
tx . Upsert ( & Item { ID : 100 })
tx . Upsert ( & Item { ID : 101 })
tx . Query (). WhereInt ( "id" , reindexer . EQ , 102 ). Set ( "Name" , "Petya" ). Update ()
// Apply transaction
if err := tx . Commit (); err != nil {
panic ( err )
}لإدراج سرعة السجلات السائبة يمكن استخدام وضع Async.
// Create new transaction object
tx , err := db . BeginTx ( "items" );
if err != nil {
panic ( err )
}
// Prepare transaction object async.
tx . UpsertAsync ( & Item { ID : 100 }, func ( err error ) {})
tx . UpsertAsync ( & Item { ID : 100 }, func ( err error ) {})
// Wait for async operations done, and apply transaction.
if err := tx . Commit (); err != nil {
panic ( err )
} الوسيطة الثانية لـ UpsertAsync هي وظيفة الإكمال ، والتي سيتم استدعاؤها بعد استلام استجابة الخادم. أيضًا ، في حالة حدوث أي خطأ أثناء إعداد عملية إعداد ، يجب على tx.Commit إرجاع خطأ. لذلك يكفي ، للتحقق من الخطأ الذي تم إرجاعه بواسطة tx.Commit - للتأكد من أن جميع البيانات قد تم ارتكابها بنجاح أم لا.
اعتمادًا على مقدار التغييرات في المعاملات ، هناك استراتيجيتين محتملتين للالتزام:
يمكن تحديد مقدار البيانات لتحديد استراتيجية الالتزام في تكوين مساحة الاسم. تحقق من الحقول StartCopyPolicyTxSize ، CopyPolicyMultiplier و TxSizeToAlwaysCopy في struct DBNamespacesConfig (defrince.go)
إذا كان حجم المعاملة أقل من TxSizeToAlwaysCopy ، فإن Reindexer يستخدم الإرشاد الإرشادي الإضافي ويحاول تجنب نسخ مساحة الاسم ، إذا لم يكن هناك استفسارات محددة لمشاهدة مساحة الاسم هذه. في بعض الحالات ، قد يزيد هذا الاستدلال من الاختيار ، لذلك قد يتم تعطيله عن طريق تعيين متغير env REINDEXER_NOTXHEURISTIC إلى أي قيمة غير فارغة.
tx.Query("ns").Exec() ... ؛يمكن لـ Rindexer الانضمام إلى المستندات من مساحات أسماء متعددة في نتيجة واحدة:
type Actor struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
IsVisible bool `reindex:"is_visible"`
}
// Fields, marked with 'joined' must also be exported - otherwise reindexer's binding will not be able to put data in those fields
type ItemWithJoin struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
ActorsIDs [] int `reindex:"actors_ids"`
ActorsNames [] int `reindex:"actors_names"`
Actors [] * Actor `reindex:"actors,,joined"`
}
... .
query := db . Query ( "items_with_join" ). Join (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors"
). On ( "actors_ids" , reindexer . SET , "id" )
it := query . Exec ()في هذا المثال ، يستخدم Reindexer الانعكاس تحت غطاء محرك السيارة لإنشاء شريحة ممثل ونسخ ممثل.
قد يكون للانضمام إلى الاستعلام من واحد إلى عدة On متصلة And (افتراضيًا) ، Or Not المشغلون:
query := db . Query ( "items_with_join" ).
Join (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Or ().
On ( "actors_names" , reindexer . SET , "name" ) يجمع InnerJoin بين البيانات من مساحين أسماء حيث توجد تطابق في حقول الانضمام في كلا مساحات الأسماء. يقوم LeftJoin بإرجاع جميع العناصر الصالحة من مساحات الأسماء على الجانب الأيسر من الكلمة الرئيسية LeftJoin ، إلى جانب القيم من الجدول على الجانب الأيمن ، أو لا شيء إذا لم يكن هناك عنصر مطابق. Join هو الاسم المستعار لـ LeftJoin .
يمكن استخدام InnerJoins كشرط في Where :
query1 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereString ( "name" , reindexer . EQ , "ActorName" ), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" )
query2 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
OpenBracket ().
InnerJoin ( db . Query ( "actors" ). WhereString ( "name" , reindexer . EQ , "ActorName" ), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
CloseBracket ()
query3 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Limit ( 0 ) لاحظ أنه عادةً Or يقوم المشغل بتنفيذ الدائرة القصيرة Where الظروف: إذا كانت الحالة السابقة صحيحة ، فلا يتم تقييم الحالة التالية. ولكن في حالة InnerJoin فإنه يعمل بشكل مختلف: في query1 (من المثال أعلاه) يتم تقييم كل من ظروف InnerJoin على الرغم من نتيجة WhereInt . Limit(0) كجزء من InnerJoin ( query3 من المثال أعلاه) لا ينضم إلى أي بيانات - فهو يعمل مثل مرشح فقط للتحقق من الظروف.
لا يدعم Reindexer ANTI JOIN SQL Construction ، ومع ذلك ، فإنه يدعم العمليات المنطقية مع توصيلات. في الواقع NOT (INNER JOIN ...) يعادل تمامًا ANTI JOIN :
query := db . Query ( "items_with_join" ).
Not ().
OpenBracket (). // Brackets are essential here for NOT to work
InnerJoin (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors" ).
On ( "id" , reindexer . EQ , "id" )
CloseBracket () SELECT * FROM items_with_join
WHERE
NOT (
INNER JOIN (
SELECT * FROM actors WHERE is_visible = true
) ON items_with_join . id = actors . id
) لتجنب استخدام التفكير ، يمكن Item تطبيق واجهة Joinable . إذا تم تنفيذ ذلك ، فإن Reindexer يستخدم هذا بدلاً من التنفيذ البطيء القائم على الانعكاس. هذا يزيد من الأداء الكلي بنسبة 10-20 ٪ ، ويقلل من كمية المخصصات.
// Joinable interface implementation.
// Join adds items from the joined namespace to the `ItemWithJoin` object.
// When calling Joinable interface, additional context variable can be passed to implement extra logic in Join.
func ( item * ItemWithJoin ) Join ( field string , subitems [] interface {}, context interface {}) {
switch field {
case "actors" :
for _ , joinItem := range subitems {
item . Actors = append ( item . Actors , joinItem .( * Actor ))
}
}
}يمكن تطبيق شرط على نتيجة استعلام آخر (مسمة فرعية) مدرجة في الاستعلام الحالي. قد تكون الحالة إما على صفوف الناتجة عن الاسم الفرعي:
query := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )أو بين مجال اسم اسم الاستعلام الرئيسي ونتيجة للاستماع الفرعي:
query := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) قد تكون نتيجة الاسم الفرعي إما حقلًا معينًا بواسطة طريقة Select (في هذه الحالة ، يجب أن يعين مرشح الحقل المفرد):
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )
query2 := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) أو عدد العناصر التي ترضي السعة الفرعية المطلوبة بواسطة طرق ReqTotal أو CachedTotal :
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). ReqTotal (), reindexer . GE , 100 )
query2 := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). CachedTotal ())أو التجميع:
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). AggregateMax ( "age" ), reindexer . GE , 33 )
query2 := db . Query ( "main_ns" ).
Where ( "age" , reindexer . GE , db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). AggregateAvg ( "age" )) Min ، Max ، Avg ، Sum ، Count و CountCached التجمعات المسموح بها فقط. لا يمكن أن تحتوي المساعد الفرعي على مجموعات متعددة في نفس الوقت.
يمكن تطبيق الانبعاثات الفرعية على نفس مساحة الاسم أو إلى آخر.
لا يمكن أن تحتوي المساعد الفرعي على مسافة فرعية أخرى أو انضمام أو دمج.
إذا كنت ترغب في التحقق مما إذا كان أحد العناصر على الأقل يرضي الفخات الفرعية ، فيمكنك استخدام ANY شرط أو EMPTY :
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ), reindexer . ANY , nil )
query2 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . LE , 18 ), reindexer . EMPTY , nil )يمكن أن يكون للوثيقة حقول متعددة كمفتاح أساسي. لتمكين هذه الميزة إضافة فهرس مركب إلى بنية. الفهرس المركب هو فهرس يتضمن حقول متعددة ، يمكن استخدامه بدلاً من عدة فهارس منفصلة.
type Item struct {
ID int64 `reindex:"id"` // 'id' is a part of a primary key
SubID int `reindex:"sub_id"` // 'sub_id' is a part of a primary key
// Fields
// ....
// Composite index
_ struct {} `reindex:"id+sub_id,,composite,pk"`
}أو
type Item struct {
ID int64 `reindex:"id,-"` // 'id' is a part of primary key, WITHOUT personal searchable index
SubID int `reindex:"sub_id,-"` // 'sub_id' is a part of a primary key, WITHOUT a personal searchable index
SubSubID int `reindex:"sub_sub_id,-"` // 'sub_sub_id' is a part of a primary key WITHOUT a personal searchable index
// Fields
// ....
// Composite index
_ struct {} `reindex:"id+sub_id+sub_sub_id,,composite,pk"`
}كما أن الفهارس المركبة مفيدة لفرز النتائج بواسطة حقول متعددة:
type Item struct {
ID int64 `reindex:"id,,pk"`
Rating int `reindex:"rating"`
Year int `reindex:"year"`
// Composite index
_ struct {} `reindex:"rating+year,tree,composite"`
}
...
// Sort query results by rating first, then by year
query := db . Query ( "items" ). Sort ( "rating+year" , true )
// Sort query results by rating first, then by year, and put item where rating == 5 and year == 2010 first
query := db . Query ( "items" ). Sort ( "rating+year" , true ,[] interface {}{ 5 , 2010 }) لإجراء الاستعلام إلى الفهرس المركب ، تمرير [] واجهة {} .WhereComposite
// Get results where rating == 5 and year == 2010
query := db . Query ( "items" ). WhereComposite ( "rating+year" , reindexer . EQ ,[] interface {}{ 5 , 2010 }) يجب فهرسة جميع الحقول في فهرس مركب منتظم (غير fulltext). على سبيل المثال ، لتكون قادرًا على إنشاء rating+year ، من الضروري إنشاء نوع من الفهارس لكل من raiting و year First:
type Item struct {
ID int64 `reindex:"id,,pk"`
Rating int `reindex:"rating,-"` // this field must be indexed (using index type '-' in this example)
Year int `reindex:"year"` // this field must be indexed (using index type 'hash' in this example)
_ struct {} `reindex:"rating+year,tree,composite"`
}يسمح Reindexer باسترداد النتائج المجمعة. حاليًا ، يتم دعم العد ، countcached ، المتوسط ، المبلغ ، الحد الأدنى ، الحد الأقصى ، الوجه والجماعات المتميزة.
Count - احصل على إجمالي عدد المستندات التي تلبي شروط QuerieCountCached - احصل على إجمالي عدد المستندات التي تلبي شروط Querie. سيتم تخزين قيمة النتيجة مخزنة مؤقتًا ويمكن إعادة استخدامها من خلال الاستعلامات الأخرى مع التجميع الكونتشيشنAggregateMax - احصل على أقصى قيمة للحقلAggregateMin - احصل على الحد الأدنى من قيمة الحقلAggregateSum - احصل على قيمة حقل المجموعAggregateAvg - احصل على متوسط قيمة الحقلAggregateFacet - الحصول على قيمة Fields FacetDistinct - احصل على قائمة بالقيم الفريدة للحقل من أجل دعم التجميع ، يحتوي Query على طرق AggregateAvg و AggregateSum و AggregateMin و AggregateMax و AggregateFacet Distinct يجب أن يتم استدعاؤها قبل تنفيذ Query : سيطلب هذا Reindexer حساب تعداد البيانات. ينطبق Aggregation Facet على أعمدة بيانات متعددة ويمكن تصنيف النتيجة بواسطة أي عمود بيانات أو "حساب" وقطعه عن طريق الإزاحة والحد. من أجل دعم طريقة الوظيفة هذه ، إرجاع AggregateFacet AggregationFacetRequest الذي يحتوي على أساليب Sort Limit Offset .
ستطبق الاستعلامات مع دمج التجمعات من الاستعلام الرئيسي على جميع الفائزين الفرعيين المدمجين. لا يمكن أن يكون لها الفخات الفرعية تجمعاتها الخاصة. التجميعات المتاحة لـ Merge-feries هي: Count ، CountCached ، Sum ، Min و Max.
للحصول على نتائج التجميع ، لدى Iterator طريقة AggResults : إنه متاح بعد تنفيذ الاستعلام وإرجاع شريحة النتائج.
رمز مثال items الإجمالية حسب price name
query := db . Query ( "items" )
query . AggregateMax ( "price" )
query . AggregateFacet ( "name" , "price" ). Sort ( "name" , true ). Sort ( "count" , false ). Offset ( 10 ). Limit ( 100 )
iterator := query . Exec ()
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
defer iterator . Close ()
aggMaxRes := iterator . AggResults ()[ 0 ]
if aggMaxRes . Value != nil {
fmt . Printf ( "max price = %d n " , * aggMaxRes . Value )
} else {
fmt . Println ( "no data to aggregate" )
}
aggFacetRes := iterator . AggResults ()[ 1 ]
fmt . Printf ( "'name' 'price' -> count" )
for _ , facet := range aggFacetRes . Facets {
fmt . Printf ( "'%s' '%s' -> %d" , facet . Values [ 0 ], facet . Values [ 1 ], facet . Count )
} query := db . Query ( "items" )
query . Distinct ( "name" ). Distinct ( "price" )
iterator := query . Exec ()
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
defer iterator . Close ()
aggResults := iterator . AggResults ()
distNames := aggResults [ 0 ]
fmt . Println ( "names:" )
for _ , name := range distNames . Distincts {
fmt . Println ( name )
}
distPrices := aggResults [ 1 ]
fmt . Println ( "prices:" )
for _ , price := range distPrices . Distincts {
fmt . Println ( price )
} يحتوي الفرز بواسطة حقول FACET المجمعة على بناء جملة مميزة في إصدار SQL:
SELECT FACET(name, price ORDER BY " name " ASC , " count " DESC ) FROM itemsيسمح ReindExer بالبحث في البيانات في حقول الصفيف عند مطابقة القيم لها مواضع الفهارس نفسها. على سبيل المثال ، لدينا مجموعة من الهياكل:
type Elem struct {
F1 int `reindex:"f1"`
F2 int `reindex:"f2"`
}
type A struct {
Elems [] Elem
}محاولة شائعة للبحث في القيم في هذه الصفيف
db . Query ( "Namespace" ). Where ( "f1" , EQ , 1 ). Where ( "f2" , EQ , 2 ) يجد جميع عناصر Array Elem[] حيث يكون f1 يساوي 1 و f2 يساوي 2.
تسمح وظيفة EqualPosition بالبحث في حقول الصفيف مع فهارس متساوية. استفسارات مثل هذا:
db . Query ( "Namespace" ). Where ( "f1" , reindexer . GE , 5 ). Where ( "f2" , reindexer . EQ , 100 ). EqualPosition ( "f1" , "f2" )أو
SELECT * FROM Namespace WHERE f1 >= 5 AND f2 = 100 EQUAL_POSITION(f1,f2); سيجد جميع عناصر صفيف Elem[] مع فهارس صفيف equal حيث يكون f1 أكبر أو تساوي 5 و f2 يساوي 100 (على سبيل المثال ، تم إرجاع الاستعلام 5 عناصر حيث يكون للعناصر الثالثة فقط من كلا الصيفين القيم المناسبة).
مع التعبيرات المعقدة (التعبيرات مع الأقواس) يمكن أن تكون equal_position () ضمن قوس:
SELECT * FROM Namespace WHERE (f1 >= 5 AND f2 = 100 EQUAL_POSITION(f1,f2)) OR (f3 = 3 AND f4 < 4 AND f5 = 7 EQUAL_POSITION(f3,f4,f5));
SELECT * FROM Namespace WHERE (f1 >= 5 AND f2 = 100 AND f3 = 3 AND f4 < 4 EQUAL_POSITION(f1,f3) EQUAL_POSITION(f2,f4)) OR (f5 = 3 AND f6 < 4 AND f7 = 7 EQUAL_POSITION(f5,f7));
SELECT * FROM Namespace WHERE f1 >= 5 AND (f2 = 100 AND f3 = 3 AND f4 < 4 EQUAL_POSITION(f2,f3)) AND f5 = 3 AND f6 < 4 EQUAL_POSITION(f1,f5,f6); لا يعمل equal_position مع الشروط التالية: هو فارغ ، فارغ وفي (مع قائمة المعلمات الفارغة).
هناك وظائف ذرية ، تنفذ تحت قفل مساحة الاسم ، وبالتالي يضمن تناسق البيانات:
يمكن تمرير هذه الوظائف لرفع/إدراج/تحديث في الوسيطات 3-RD والقادمة.
إذا تم توفير هذه الوظائف ، فسيتم تغيير العنصر المرجعي إلى القيمة المحدثة
// set ID field from serial generator
db . Insert ( "items" , & item , "id=serial()" )
// set current timestamp in nanoseconds to updated_at field
db . Update ( "items" , & item , "updated_at=now(NSEC)" )
// set current timestamp and ID
db . Upsert ( "items" , & item , "updated_at=now(NSEC)" , "id=serial()" )يعد انتهاء صلاحية البيانات مفيدًا لبعض فئات المعلومات ، بما في ذلك بيانات الأحداث التي تم إنشاؤها من الجهاز والسجلات ومعلومات الجلسة التي تحتاج فقط إلى الاستمرار لفترة زمنية محدودة.
يتيح ReindExer تعيين TTL (حان الوقت للعيش) لعناصر مساحة الاسم. يؤدي إضافة TTLindex إلى مساحة الاسم تلقائيًا إلى إزالة العناصر بعد عدد محدد من الثواني.
تعمل فهارس TTL فقط مع حقول INT64 وتخزين بيانات الطابع الزمني UNIX. تنتهي العناصر التي تحتوي على فهرس TTL بعد expire_after Seconds. مثال على إعلان ttlindex في Golang:
type NamespaceExample struct {
ID int `reindex:"id,,pk" json:"id"`
Date int64 `reindex:"date,ttl,,expire_after=3600" json:"date"`
}
...
ns . Date = time . Now (). Unix ()في هذه الحالة ، تنتهي عناصر مساحة الاسم المساحة ، فالقملة بعد 3600 ثانية بعد قيمة مساحة الاسم.
يدعم مؤشر TTL الاستعلامات بنفس الطريقة التي تفعل بها فهارس غير TTL.
إذا كانت بيانات المصدر متوفرة بتنسيق JSON ، فمن الممكن تحسين أداء العمليات الجذعية/الحذف عن طريق تمرير JSON مباشرة إلى Reindexer. سيتم إلغاء التخلص من JSON بواسطة رمز C ++ ، دون تخصيص/إزالة الإرهاق في رمز GO.
يمكن أن تعالج وظائف الانتصار أو الحذف JSON فقط عن طريق تمرير [] حجة بايت مع JSON
json := [] byte ( `{"id":1,"name":"test"}` )
db . Upsert ( "items" , json )إنها مجرد ما يعادل:
item := & Item {}
json . Unmarshal ([] byte ( `{"id":1,"name":"test"}` ), item )
db . Upsert ( "items" , item )في حالة حدوث متطلبات لتسلسل نتائج الاستعلام بتنسيق JSON ، فمن الممكن تحسين الأداء من خلال الحصول على النتائج مباشرة في تنسيق JSON من Reindexer. سيتم إجراء تسلسل JSON بواسطة رمز C ++ ، دون تخصيص/تسلسل إضافي في رمز GO.
...
iterator := db . Query ( "items" ).
Select ( "id" , "name" ). // Filter output JSON: Select only "id" and "name" fields of items, another fields will be omitted. This fields should be specified in the same case as the jsonpaths corresponding to them.
Limit ( 1 ).
ExecToJson ( "root_object" ) // Name of root object of output JSON
json , err := iterator . FetchAll ()
// Check the error
if err != nil {
panic ( err )
}
fmt . Printf ( "%s n " , string ( json ))
...سيطبع هذا الرمز شيئًا مثل:
{ "root_object" : [{ "id" : 1 , "name" : " test " }] } لتجنب ظروف السباق ، يتم إيقاف تشغيل ذاكرة التخزين المؤقت للكائنات الافتراضية بشكل افتراضي ويتم تخصيص جميع الكائنات وتخصيصها من التنسيق الداخلي للقتال (تسمى CJSON ) لكل استعلام. إن التخلص من التالي هو الانعكاس ، لذلك فإن سرعته ليست مثالية (في الواقع ، فإن إزالة التخلص من CJSON أسرع من 3 إلى 10 أضعاف من JSON ، و ~ 1.2x أسرع من GOB ) ، ولكن لا يزال الأداء محدودًا بشكل خطير بسبب الانعكاس.
هناك طريقتان لتمكين ذاكرة التخزين المؤقت للكائن:
إذا كان الكائن ينفذ واجهة DeepCopy ، فسيقوم Reindexer بتشغيل ذاكرة التخزين المؤقت للكائنات واستخدام واجهة DeepCopy لنسخ الكائنات من ذاكرة التخزين المؤقت إلى نتائج الاستعلام. واجهة DeepCopy هي المسؤولة عن إنشاء نسخة عميقة من كائن المصدر.
فيما يلي عينة من تنفيذ واجهة العميق
func ( item * Item ) DeepCopy () interface {} {
copyItem := & Item {
ID : item . ID ,
Name : item . Name ,
Articles : make ([] int , cap ( item . Articles ), len ( item . Articles )),
Year : item . Year ,
}
copy ( copyItem . Articles , item . Articles )
return copyItem
} لتسريع الاستعلامات وعدم تخصيص كائنات جديدة لكل كل استعلام ، يمكن طرح كائنات إرجاع الاستعلام مباشرة من ذاكرة التخزين المؤقت للكائن. لتمكين هذا السلوك ، اتصل بـ AllowUnsafe(true) على Iterator .
تحذير: عند استخدام الاستعلامات AllowUnsafe(true) ، تُرجع الاستعلامات المشتركة مؤشرات مشتركة للهياكل في ذاكرة التخزين المؤقت للكائن. لذلك ، يجب ألا يعدل التطبيق الكائنات التي تم إرجاعها.
res , err := db . Query ( "items" ). WhereInt ( "id" , reindexer . EQ , 1 ). Exec (). AllowUnsafe ( true ). FetchAll ()
if err != nil {
panic ( err )
}
if len ( res ) > 1 {
// item is SHARED pointer to struct in object cache
item = res [ 0 ].( * Item )
// It's OK - fmt.Printf will not modify item
fmt . Printf ( "%v" , item )
// It's WRONG - can race, and will corrupt data in object cache
item . Name = "new name"
} بشكل افتراضي ، يبلغ الحد الأقصى لحجم ذاكرة التخزين المؤقت للكائن 256000 عنصر لكل مساحة اسم. لتغيير الحد الأقصى للحجم ، استخدم طريقة ObjCacheSize من NamespaceOptions ، التي تم تمريرها إلى OpenNamesPace. على سبيل المثال
// Set object cache limit to 4096 items
db . OpenNamespace ( "items_with_huge_cache" , reindexer . DefaultNamespaceOptions (). ObjCacheSize ( 4096 ), Item {})! لا ينبغي استخدام ذاكرة التخزين المؤقت هذه لمساحات الأسماء ، والتي تم تكرارها من العقد الأخرى: قد يكون غير متسق لمساحات أسماء النسخة المتماثلة.
نوع بيانات الهندسة الوحيد المدعوم هو 2D Point ، الذي تم تنفيذه في Golang كـ [2]float64 ( reindexer.Point ).
في SQL ، يمكن إنشاء نقطة كـ ST_GeomFromText('point(1 -3)') .
الطلب الوحيد المدعوم لحقل الهندسة هو العثور على جميع النقاط على مسافة من نقطة. DWithin(field_name, point, distance) على سبيل المثال أدناه.
وظيفة SQL المقابلة هي ST_DWithin(field_name, point, distance) .
يمكن إنشاء مؤشر RTREE للنقاط. للقيام بذلك ، يجب الإعلان عن rtree و linear أو quadratic أو greene أو rstar . تعني linear أو quadratic أو greene أو rstar أي خوارزمية بناء RTREE سيتم استخدامها. هنا يتم سرد الخوارزميات بالترتيب من الأمثل للإدراج إلى الأمثل للبحث. لكنه يعتمد على البيانات. اختبار ما هو أكثر ملاءمة لك. الخوارزمية الافتراضية هي rstar .
type Item struct {
id int `reindex:"id,,pk"`
pointIndexed reindexer. Point `reindex:"point_indexed,rtree,linear"`
pointNonIndexed reindexer. Point `json:"point_non_indexed"`
}
query1 := db . Query ( "items" ). DWithin ( "point_indexed" , reindexer. Point { - 1.0 , 1.0 }, 4.0 ) SELECT * FROM items WHERE ST_DWithin(point_non_indexed, ST_GeomFromText( ' point(1 -3.5) ' ), 5 . 0 ); يمكن تشغيل مسجل Rindexer بواسطة طريقة db.SetLogger() ، تمامًا كما هو الحال في مقتطف الكود هذا:
type Logger struct {
}
func ( Logger ) Printf ( level int , format string , msg ... interface {}) {
log . Printf ( format , msg ... )
}
...
db . SetLogger ( Logger {}) يدعم Reindexer تسجيل الإجراءات البطيئة. يمكن تكوينه عبر profiling.long_queries_logging من مساحة اسم نظام #config . يمكن تكوين تسجيل الإجراءات التالية:
حدد الاستعلامات:
threshold_us (integer) : قيمة العتبة (في microseconds) لتنفيذ استعلام محدد. إذا تم تجاوزه ، فسيتم إدخال السطوع الأساسي ، إذا كان threshold_us هو -1 تسجيل الدخول.normalized (boolean) : إخراج الاستعلام في شكل طبيعي.تحديث وحذف الاستعلامات:
threshold_us (integer) : قيمة العتبة (في microseconds) لتنفيذ التحديث أو حذف الاستعلام. إذا تم تجاوزه ، فسيتم إدخال السطوع الأساسي ، إذا كان threshold_us هو -1 تسجيل الدخول.normalized (boolean) : إخراج الاستعلام في شكل طبيعي.المعاملات:
threshold_us (integer) : قيمة العتبة (في microseconds) لإجمالي وقت التزام المعاملات ، إذا كان threshold_us هو -1 تسجيل من خلال وقت التزام المعاملات الكلي.avg_step_threshold_us (integer) : قيمة العتبة (في microseconds) لمتوسط مدة الخطوة في المعاملة. إذا كان avg_step_threshold_us هو -1 تسجيل حسب متوسط مدة خطوة المعاملة.ميزة أخرى مفيدة هي تصحيح طباعة الاستعلامات المعالجة. لتصحيح تفاصيل استفسارات الطباعة هناك طريقتان:
db.SetDefaultQueryDebug(namespace string,level int) - إنها تتيح على مستوى العالم تفاصيل الطباعة لجميع الاستعلامات عن طريق مساحة الاسم
query.Debug(level int) - تفاصيل طباعة level تنفيذ الاستعلام هو مستوى الأناقة:
reindexer.INFO - سوف يطبع ظروف الاستعلام فقط
reindexer.TRACE - سوف يطبع شروط الاستعلام وتفاصيل التنفيذ مع توقيتات
query.Explain () - حساب وتفاصيل تنفيذ الاستعلام وتخزينها.
iterator.GetExplainResults () - إرجاع تفاصيل تنفيذ الاستعلام
Reindexer has support for TCMalloc (which is also a part of GPerfTools) and JEMalloc allocators (check ENABLE_TCMALLOC and ENABLE_JEMALLOC in CMakeLists.txt).
If you have built standalone server from sources available allocators will be detected and used automatically.
In go:generate builds and prebuilt packages reindexer has TCMalloc support, however none of TCMalloc libraries will be linked automatically. To force allocator's libraries linkage LD_PRELOAD with required library has to be used:
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_and_profiler.so ./my_executable
Custom allocator may be handy to track memory consummation, profile heap/CPU or to improve general performance.
Because reindexer core is written in C++ all calls to reindexer and their memory consumption are not visible for go profiler. To profile reindexer core there are cgo profiler available. cgo profiler now is part of reindexer, but it can be used with any another cgo code.
Usage of cgo profiler is very similar with usage of go profiler.
import _ "github.com/restream/reindexer/v3/pprof" go func () {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ))
}()HEAPPROFILE=/tmp/pprofpprof -symbolize remote http://localhost:6060/debug/cgo/pprof/heapInternal Reindexer's profiler is based on gperf_tools library and unable to get CPU profile via Go runtime. However, go profiler may be used with symbolizer to retrieve C++ CPU usage.
import _ "net/http/pprof" go func () {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ))
}()REINDEXER_CGOBACKTRACE=1pprof -symbolize remote http://localhost:6060/debug/pprof/profile ? seconds=10Due to internal Golang's specific it's not recommended to try to get CPU and heap profiles simultaneously, because it may cause deadlock inside the profiler.
Go binding for Reindexer comes with optional support for OpenTelemetry integration.
To enable generation of OpenTelemetry tracing spans for all exported client side calls ( OpenNamespace , Upsert , etc), pass reindexer.WithOpenTelemetry() option when creating a Reindexer DB instance:
db := reindexer . NewReindex ( "cproto://user:[email protected]:6534/testdb" , reindexer . WithOpenTelemetry ()) All client side calls on the db instance will generate OpenTelemetry spans with the name of the performed operation and information about Reindexer DSN, namespace name (if applicable), etc.
For example, a call like this on the db instance above:
db . OpenNamespace ( "items" , reindexer . DefaultNamespaceOptions (), Item {}) will generate an OpenTelemetry span with the span name of Reindexer.OpenNamespace and with span attributes like this:
rx.dsn : cproto://user:[email protected]:6534/testdbrx.ns : items Use opentelemetry-go in your client go application to export the information externally. For example, as a minimum, you will need to configure OpenTelemetry SDK exporter to expose the generated spans externally (see the Getting Started guide for more information).
A list of connectors for work with Reindexer via other program languages (TBC later):
Pyreindexer is official connector, and maintained by Reindexer's team. It supports both builtin and standalone modes. Before installation reindexer-dev (version >= 2.10) should be installed. See installation instructions for details.
For install run:
pip3 install pyreindexerURLs:
Python version >=3.6 is required.
Reindexer for java is official connector, and maintained by Reindexer's team. It supports both builtin and standalone modes. For enable builtin mode support reindexer-dev (version >= 3.1.0) should be installed. See installation instructions for details.
For install reindexer to Java or Kotlin project add the following lines to maven project file
<dependency>
<groupId>com.github.restream</groupId>
<artifactId>rx-connector</artifactId>
<version>[LATEST_VERSION]</version>
</dependency>
URL: https://github.com/Restream/reindexer-java
Note: Java version >= 1.8 is required.
Spring wrapper for Java-connector: https://github.com/evgeniycheban/spring-data-reindexer
URL: https://github.com/Smolevich/reindexer-client
URL: https://github.com/coinrust/reindexer-rs
URL: https://github.com/oruchreis/ReindexerNet
Currently, Reindexer is stable and production ready, but it is still a work in progress, so there are some limitations and issues:
You can get help in several ways:
Landing: https://reindexer.io/
Packages repo: https://repo.reindexer.io/
More documentation (RU): https://reindexer.io/reindexer-docs/