From be4a413091eef3837a3036da7bf167a1c8c80f83 Mon Sep 17 00:00:00 2001 From: David Hall Date: Thu, 31 Aug 2017 17:02:03 -0500 Subject: [PATCH] MCOL-523 UDAF documentation --- utils/udfsdk/docs/make.bat | 281 +++++++++++++++ .../source/_static/MariaDB_Logo_under.png | Bin 0 -> 6487 bytes .../udfsdk/docs/source/_static/cc-symbol.png | Bin 0 -> 5083 bytes utils/udfsdk/docs/source/_static/mariadb.png | Bin 0 -> 35062 bytes utils/udfsdk/docs/source/changelog.rst | 8 + utils/udfsdk/docs/source/conf.py | 334 ++++++++++++++++++ utils/udfsdk/docs/source/index.rst | 20 ++ utils/udfsdk/docs/source/license.rst | 16 + .../docs/source/reference/ByteStream.rst | 78 ++++ .../docs/source/reference/ColumnDatum.rst | 120 +++++++ .../udfsdk/docs/source/reference/UDAFMap.rst | 14 + .../udfsdk/docs/source/reference/UserData.rst | 39 ++ utils/udfsdk/docs/source/reference/api.rst | 20 ++ utils/udfsdk/docs/source/reference/index.rst | 14 + .../docs/source/reference/mcsv1Context.rst | 309 ++++++++++++++++ .../docs/source/reference/mcsv1_UDAF.rst | 196 ++++++++++ utils/udfsdk/docs/source/usage/headerfile.rst | 37 ++ utils/udfsdk/docs/source/usage/index.rst | 11 + .../udfsdk/docs/source/usage/introduction.rst | 22 ++ .../docs/source/usage/memoryallocation.rst | 96 +++++ utils/udfsdk/docs/source/usage/sourcefile.rst | 261 ++++++++++++++ 21 files changed, 1876 insertions(+) create mode 100644 utils/udfsdk/docs/make.bat create mode 100644 utils/udfsdk/docs/source/_static/MariaDB_Logo_under.png create mode 100644 utils/udfsdk/docs/source/_static/cc-symbol.png create mode 100644 utils/udfsdk/docs/source/_static/mariadb.png create mode 100644 utils/udfsdk/docs/source/changelog.rst create mode 100644 utils/udfsdk/docs/source/conf.py create mode 100644 utils/udfsdk/docs/source/index.rst create mode 100644 utils/udfsdk/docs/source/license.rst create mode 100644 utils/udfsdk/docs/source/reference/ByteStream.rst create mode 100644 utils/udfsdk/docs/source/reference/ColumnDatum.rst create mode 100644 utils/udfsdk/docs/source/reference/UDAFMap.rst create mode 100644 utils/udfsdk/docs/source/reference/UserData.rst create mode 100644 utils/udfsdk/docs/source/reference/api.rst create mode 100644 utils/udfsdk/docs/source/reference/index.rst create mode 100644 utils/udfsdk/docs/source/reference/mcsv1Context.rst create mode 100644 utils/udfsdk/docs/source/reference/mcsv1_UDAF.rst create mode 100644 utils/udfsdk/docs/source/usage/headerfile.rst create mode 100644 utils/udfsdk/docs/source/usage/index.rst create mode 100644 utils/udfsdk/docs/source/usage/introduction.rst create mode 100644 utils/udfsdk/docs/source/usage/memoryallocation.rst create mode 100644 utils/udfsdk/docs/source/usage/sourcefile.rst diff --git a/utils/udfsdk/docs/make.bat b/utils/udfsdk/docs/make.bat new file mode 100644 index 000000000..423c8b2c5 --- /dev/null +++ b/utils/udfsdk/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\UDAF.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\UDAF.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/utils/udfsdk/docs/source/_static/MariaDB_Logo_under.png b/utils/udfsdk/docs/source/_static/MariaDB_Logo_under.png new file mode 100644 index 0000000000000000000000000000000000000000..5658cdead830f2d8ed13e55c314f31c42474adc4 GIT binary patch literal 6487 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 literal 0 HcmV?d00001 diff --git a/utils/udfsdk/docs/source/_static/mariadb.png b/utils/udfsdk/docs/source/_static/mariadb.png new file mode 100644 index 0000000000000000000000000000000000000000..c92dc8da41d10ff25cdac05a6720988b12039ed0 GIT binary patch literal 35062 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 v documentation" by default. +# +# html_title = u'UDAF v1.1' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'UDAFdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', + 'classoptions': ',openany,oneside' +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'UDAF.tex', u'UDAF Documentation', + u'MariaDB Corporation', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'udaf', u'UDAF Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'UDAF', u'UDAF Documentation', + author, 'UDAF', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/utils/udfsdk/docs/source/index.rst b/utils/udfsdk/docs/source/index.rst new file mode 100644 index 000000000..782343cf5 --- /dev/null +++ b/utils/udfsdk/docs/source/index.rst @@ -0,0 +1,20 @@ +.. |vspace| raw:: latex + + \vspace{5mm} + +.. |br| raw:: html + +
+ + +Welcome to the UDAF API documentation +===================================== + +.. toctree:: + :maxdepth: 4 + + license + changelog + usage/index + reference/index + diff --git a/utils/udfsdk/docs/source/license.rst b/utils/udfsdk/docs/source/license.rst new file mode 100644 index 000000000..7e40d5be1 --- /dev/null +++ b/utils/udfsdk/docs/source/license.rst @@ -0,0 +1,16 @@ +Licensing +========= + +Documentation Content +--------------------- + +.. figure:: /_static/cc-symbol.png + :alt: Creative Commons License + :target: http://creativecommons.org/licenses/by-sa/4.0/ + +The mcsv1_UDAF documentation is licensed under a `Creative Commons Attribution-ShareAlike 4.0 International License `_. + +MariaDB ColumnStore C++ API +--------------------------- + +The MariaDB ColumnStore UDAF C++ API (mcsv1_udaf) is licensed under the `GNU General Public License, version 2.0 `_. diff --git a/utils/udfsdk/docs/source/reference/ByteStream.rst b/utils/udfsdk/docs/source/reference/ByteStream.rst new file mode 100644 index 000000000..de9a07797 --- /dev/null +++ b/utils/udfsdk/docs/source/reference/ByteStream.rst @@ -0,0 +1,78 @@ +ByteStream +========== + +ByteStream is class in Columnstore used to stream objects for transmission over TCP. It contains a binary buffer and set of methods and operators for streaming data into the buffer. + +Bytestream is compiled and lives in the messageqcpp shared library. The header can be found in the Columnstore engine source tree at utils/messageqcpp/bytestream.h + +#include "bytestream.h" + +For UDA(n)F you should not have to instantiate a ByteStream. However, if you implement the Complex Data Model, you will need to use the instances passed to your serialize() and unserialize() methods. + +These typedefs exist only for backwards compatibility and can be ignored: + +* typedef uint8_t byte; +* typedef uint16_t doublebyte; +* typedef uint32_t quadbyte; +* typedef uint64_t octbyte; +* typedef boost::uuids::uuid uuid; + +uuid may be a useful short cut if you need to stream a boost uuid, but otherwise, use the C++ standard int8_t, int16_t, etc. + +.. rubric:: Buffer Allocation + +ByteStream reallocates as necessary. To lower the number of allocations required, you can use the needAtLeast(size_t) method to preallocate your minimum needs. + +.. rubric:: Basic Streaming + +For each basic data type of int8_t, int16_t, int32_t, int64_t, float, double, uuid, std::string and the unsigned integer counterparts, there are streaming operators '<<' and '>>' defined. + +In addition, if a class is derived from serializable, it can be streamed in toto with the '<<' and '>>' operators. + +There are also peek() methods for each data type to allow you to look at the top of the stream without removing the value:: + + int32_t val; + void peek(val); + will put the top 4 bytes, interpreted as an int32_t into val and leave the data in the stream. + +This works for std::string:: + + std::string str; + void peek(str); + +To put a binary buffer into the stream, use append. It is usually wise to put the length of the binary data into the astream before the binary data so you can get the length when unserializing:: + + bs << len; + bs.append(reinterpret_cast(mybuf), len); + + +To get binary data out of the stream, copy the data out and advance() the buffer:: + + bs >> len; + memcpy(mybuf, bs.buf(), len); // TODO: Be sure mybuf is big enough + bs.advance(len); + +.. rubric:: Utility funcions + +To determine if there's data in the stream:: + + bs.empty(); + +To determine the raw length of the data in the stream:: + + bs.length(); + +If for some reason, you want to empty the buffer, say if some unlikely logic causes your serialize() method to follow a new streaming logic:: + + bs.restart(); + +To get back to the beginning while unserializing:: + + bs.rewind(); + +And finally, to just start over, releasing the allocated buffer and allocating a new one:: + + bs.reset(); + + + diff --git a/utils/udfsdk/docs/source/reference/ColumnDatum.rst b/utils/udfsdk/docs/source/reference/ColumnDatum.rst new file mode 100644 index 000000000..564b24e44 --- /dev/null +++ b/utils/udfsdk/docs/source/reference/ColumnDatum.rst @@ -0,0 +1,120 @@ +ColumnDatum +=========== + +Since aggregate functions can operate on any data type, we use the ColumnDatum struct to define the input row data. To be type insensiteve, data is stored in type static_any::any. + +To access the data it must be type cast to the correct type using static_any::any::cast(). + +Example for int data: + +:: + + if (valIn.compatible(intTypeId) + int myint = valIn.cast(); + + +For multi-paramter aggregations (not available in Columnstore 1.1), the colsIn vector of next_value() contains the ordered set of row parameters. + +For char, varchar, text, varbinary and blob types, columnData will be std::string. + +The intTypeId above is one of a set of "static const static_any::any&" provided in mcsv1_UDAF for your use to figure out which type of data was actually sent. See the MEDIAN example for how these might be used to accept any numeric data type. + +The provided values are: + +* charTypeId +* scharTypeId +* shortTypeId +* intTypeId +* longTypeId +* llTypeId +* ucharTypeId +* ushortTypeId +* uintTypeId +* ulongTypeId +* ullTypeId +* floatTypeId +* doubleTypeId +* strTypeId + +.. rubric:: The ColumnDatum members. + +.. c:member:: CalpontSystemCatalog::ColDataType dataType; + + ColDataType is defined in calpontsystemcatalog.h and can be any of the following: + +.. _coldatatype: + +.. list-table:: ColDataType + :widths: 10 50 + :header-rows: 1 + + * - Data Type + - Usage + * - BIT + - Represents a binary 1 or 0. Stored in a single byte. + * - TINYINT + - A signed one byte integer + * - CHAR + - A signed one byte integer or an ascii char + * - SMALLINT + - A signed two byte integer + * - DECIMAL + - A Columnstore Decimal value. For Columnstore 1.1, this is stored in the smallest integer type field that will hold the required precision. + * - MEDINT + - A signed four byte integer + * - INT + - A signed four byte integer + * - FLOAT + - A floating point number. Represented as a C++ float type. + * - DATE + - A Columnstore date stored as a four byte unsigned integer. + * - BIGINT + - A signed eight byte integer + * - DOUBLE + - A floating point number. Represented as a C++ double type. + * - DATETIME + - A Columnstore date-time stored as an eight byte unsigned integer. + * - VARCHAR + - A mariadb variable length string. Represented a std::string + * - VARBINARY + - A mariadb variable length binary. Represented a std::string that may contain embedded '0's + * - CLOB + - Has not been verified for use in UDAF + * - BLOB + - Has not been verified for use in UDAF + * - UTINYINT + - An unsigned one byte integer + * - USMALLINT + - An unsigned two byte integer + * - UDECIMAL + - DECIMAL, but no negative values allowed. + * - UMEDINT + - An unsigned four byte integer + * - UINT + - An unsigned four byte integer + * - UFLOAT + - FLOAT, but no negative values allowed. + * - UBIGINT + - An unsigned eight byte integer + * - UDOUBLE + - DOUBLE, but no negative values allowed. + * - TEXT + - Has not been verified for use in UDAF + +.. c:member:: static_any::any columnData; + + Holds the value for this column in this row. + +.. c:member:: uint32_t scale; + + If dataType is a DECIMAL type + +.. c:member:: uint32_t precision; + + If dataType is a DECIMAL type + +.. c:function:: ColumnDatum() + + Sets defaults. + + diff --git a/utils/udfsdk/docs/source/reference/UDAFMap.rst b/utils/udfsdk/docs/source/reference/UDAFMap.rst new file mode 100644 index 000000000..74fd31a84 --- /dev/null +++ b/utils/udfsdk/docs/source/reference/UDAFMap.rst @@ -0,0 +1,14 @@ +UDAFMap +------- + +UDAFMap holds a mapping from the function name to its implementation. The engine uses the map when a UDA(n)F is called by a SQL statement. + +You must enter your function into the map. This means you must: + +* #include your header file +* add an entry into UDAFMap::getMap(). + +The map is fully populated the first time it is called, i.e., the first time any UDA(n)F is called by a SQL statement. + +The need for you to manually enter your function into this map may be alleviated by future enhancements. + diff --git a/utils/udfsdk/docs/source/reference/UserData.rst b/utils/udfsdk/docs/source/reference/UserData.rst new file mode 100644 index 000000000..b9e28e868 --- /dev/null +++ b/utils/udfsdk/docs/source/reference/UserData.rst @@ -0,0 +1,39 @@ +UserData +-------- + +The UserData struct is used for allocating and streaming the intermediate data needed by the UDA(n)F. UserData is function specific instance data. + +There may be multiple instantiations of UserData for each UDA(n)F call. Make no assumptions about the contents except within the context of a method call. + +Since there is no need to deal with the UserData Structure unless you are using the Complex Data Mode, the remainder of this page assumes the Complex Data Structure. + +To use the Complex Data Structure, you derive a struct from UserData and override, at the least, the streaming functions. + +.. rubric:: constructor + +.. c:function:: UserData(); + + Constructors are called by your code, so adding other constructors is acceptable. + +.. rubric:: destructor + +.. c:function:: virtual ~UserData(); + + Be sure to cleanup any internal data structures or containers you may have populated that don't have automatic cleanup. + +.. rubric:: Streaming methods + +As data is passed between processes and modules, it must be streamed. This is because complex data structures are generally not stored in sequential bytes of memory. For instance, in a std::map container, each of its elements is stored discretely wherever the OS puts them. Streaming allows us to take all those disparate data pieces and put them on the wire as one unit. + +.. c:function:: virtual void serialize(messageqcpp::ByteStream& bs) const; + +:param bs: A ByteStream object to which you stream the UserData values. + + Stream all the UserData to a ByteStream. See the section on ByteStream for the usage of that object. + +.. c:function:: virtual void unserialize(messageqcpp::ByteStream& bs); + +:param bs: A ByteStream object from which you stream the UserData values, + + Unstream the data from the ByteStream and put it back into the data structure. This method **must** exactly match the serialize() implementation. + diff --git a/utils/udfsdk/docs/source/reference/api.rst b/utils/udfsdk/docs/source/reference/api.rst new file mode 100644 index 000000000..3b8635a20 --- /dev/null +++ b/utils/udfsdk/docs/source/reference/api.rst @@ -0,0 +1,20 @@ +API Reference +============= + +The UDAF API consists of a set of classes and structures used to implement a MariaDB Columnstore UDAF and/or UDAnF. + +The classes and structures are: + +* class UDAFMap +* struct UserData +* class mcsv1Context +* struct ColumnDatum +* class mcsv1_UDAF + +The following Columnstore classes are also used: + +* execplan::CalpontSystemCatalog::ColDataType +* messageqcpp::ByteStream + +We also define COL_TYPES as a vector of pairs containing the column name and it's type. + diff --git a/utils/udfsdk/docs/source/reference/index.rst b/utils/udfsdk/docs/source/reference/index.rst new file mode 100644 index 000000000..2bfc361be --- /dev/null +++ b/utils/udfsdk/docs/source/reference/index.rst @@ -0,0 +1,14 @@ +mcsv1_udaf reference +==================== + +.. toctree:: + :maxdepth: 3 + + api + UDAFMap + UserData + mcsv1Context + ColumnDatum + mcsv1_UDAF + ByteStream + diff --git a/utils/udfsdk/docs/source/reference/mcsv1Context.rst b/utils/udfsdk/docs/source/reference/mcsv1Context.rst new file mode 100644 index 000000000..76929056f --- /dev/null +++ b/utils/udfsdk/docs/source/reference/mcsv1Context.rst @@ -0,0 +1,309 @@ +mcsv1Context -- the Context object +---------------------------------- + +The class mcsv1Context holds all the state data for a given instance of the UDAF. There are parts that are set by user code to select options, and parts that are set by the Framework to tell you what is happening for a specific method call. + +The class is designed to be accessed via functions. There should be no situations that require direct manipulation of the data items. To do so could prove counter-productive. + +There are a set of public functions tagged "For use by the framework". You should not use these functions. To do so could prove counter-productive. + +An instance of mcsv1Context is created and sent to your init() method. Here is where you set options and the like. Thereafter, a context is sent to each method. It will contain the options you set as well as state data specific for that call. However, it will not be the original context, but rather a copy. init() is called by the mysqld process. Other methods are called by ExeMgr (UM) and PrimProc (PM). The context is streamed from process to process and even within processes, it may be copied for multi-thread processing. + +The mcsv1Context member functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. rubric:: constructor + +.. c:function:: mcsv1Context(); + + Sets some defaults. No magic here. + + EXPORT mcsv1Context(const mcsv1Context& rhs); + + Copy contstructor. The engine uses this to copy the context object for various reasons. You should not need to ever construct or copy a context object. + +.. rubric:: destructor + +.. c:function:: virtual ~mcsv1Context(); + + The destructor is virtual only in case a version 2 is made. This supports backward compatibility. mcsv1Context should never be subclassed by UDA(n)F developers. + +.. rubric:: Error message handling + +.. c:function:: void setErrorMessage(std::string errmsg); + +:param errmsg: The error message you would like to display. +:returns: nothing + + If an error occurs during your processing, you can send an error message to the system that will get displayed on the console and logged. You should also return ERROR from the method that set the error message. + +.. c:function:: const std::string& getErrorMessage() const; + +:returns: The current error message, if set. + + In case you want to know the current error message. In most (all?) cases, this will be an empty string unless you set it earlier in the same method, but is included for completeness. + +.. _runflags: + +.. rubric:: Runtime Flags + +The runtime flags are 8 byte bit flag field where you set any options you need for the run. Set these options in your init() method. Setting flags outside of the init() method may have unintended consequences. + +The following table lists the possible option flags. Many of these flags affect the behavior of UDAnF (Analytic Window Functions). Most UDAF can also be used as Window Functions as well. The flags that affect UDAnF are ignored when called as an aggregate function. + +.. list-table:: Option Flags + :widths: 20 5 30 + :header-rows: 1 + + * - Option Name + - Default + - Usage + * - UDAF_OVER_REQUIRED + - Off + - Tells the system to reject any call as an aggregate function. This function is a Window Function only. + * - UDAF_OVER_ALLOWED + - On + - Tells the systen this function may be called as an aggregate or as a Window Function. UDAF_OVER_REQUIRED takes precedence. If not set, then the function may only be called as an aggregate function. + * - UDAF_ORDER_REQUIRED + - Off + - Tells the system that if the function is used as a Window Function, then it must have an ORDER BY clause in the Window description. + * - UDAF_ORDER_ALLOWED + - On + - Tells the system that if the function is used as a Window Function, then it may optionally have an ORDER BY clause in the Window description. If this flag is off, then the function may not have an ORDER BY clause. + * - UDAF_WINDOWFRAME_REQUIRED + - Off + - Tells the system that if the function is used as a Window Function, then it must have a Window Frame defined. The Window Frame clause is the PRECEDING and FOLLOWING stuff. + * - UDAF_WINDOWFRAME_ALLOWED + - On + - Tells the system that if the function is used as a Window Function, then it may optionally have a Window Frame defined. If this flag is off, then the function may not have a Window Frame clause. + * - UDAF_MAYBE_NULL + - On + - Tells the system that the function may return a null value. Currently, this flag is ignored. + * - UDAF_IGNORE_NULLS + - On + - Tells the system not to send NULL values to the function. They will be ignored. If off, then NULL values will be sent to the function. + +.. c:function:: uint64_t setRunFlags(uint64_t flags); + +:param flags: The new set of run flags to be used for this instance. +:returns: The previous set of flags. + + Replace the current set of flags with the new set. In general, this shouldn't be done unless you are absolutely sure. Rather, use setRunFlag() to set set specific options and clearRunFlag to turn of specific options. + +.. c:function:: uint64_t getRunFlags() const; + +:returns: The current set of run flags. + + Get the integer value of the run flags. You can use the result with the '|' operator to determine the setting of a specific option. + +.. c:function:: bool setRunFlag(uint64_t flag); + +:param flag: The flag or flags ('|' together) to be set. +:returns: The previous setting of the flag or flags (using &) + + Set a specific run flag or flags. + + Ex: setRunFlag(UDAF_OVER_REQUIRED | UDAF_ORDER_REQUIRED); + +.. c:function:: bool getRunFlag(uint64_t flag); + +:param flag: A flag or flags ('|' together) to get the value of. +:returns: The value of the flag or flags ('&' together). + + Get a specific flags value. If used with multiple flag values joined with the '|' operator, then all flags listed must be set for a true return value. + +.. c:function:: bool clearRunFlag(uint64_t flag); + +:param flag: A flag or flags ('|' together) to be cleared. +:returns: The previous value of the flag or flags ('&' together). + + Clear a specific flag and return it's previous value. If multiple flags are listed joined with the '|' operator, then all listed flags are cleared and will return true only if all flags had been set. + +.. c:function:: bool toggleRunFlag(uint64_t flag); + +:param flag: A flag to be toggled. +:returns: The previous value of the flag. + + Toggle a flag and return its previous setting. + +.. rubric:: Runtime Environment + +Use these to determine the way your UDA(n)F was called + +.. c:function:: bool isAnalytic(); + +:returns: true if the current call of the function is as a Window Function. + +.. c:function:: bool isWindowHasCurrentRow(); + +:returns: true if the Current Row is in the Window. This is usually true but may not be for some Window Frames such as, for exampe, BETWEEEN UNBOUNDED PRECEDING AND 2 PRECEDDING. + +.. c:function:: bool isUM(); + +:returns: true if the call is from the UM. + +.. c:function:: bool isPM(); + +:returns: true if the call is from the PM. + +.. rubric:: Parameter refinement description accessors + +.. c:function:: size_t getParameterCount() const; + +:returns: the number of parameters to the function in the SQL query. Columnstore 1.1 only supports one parameter. + +.. c:function:: bool isParamNull(int paramIdx); + +:returns: true if the parameter is NULL for the current row. + +.. c:function:: bool isParamConstant(int paramIdx); + +:returns: true if the parameter is a constant. + +.. c:function:: uint64_t getRowsInPartition() const; + +:returns: the actual number of rows in the Window partition. If no partitioning, returns 0. + +.. c:function:: cuint64_t getRowCnt() const; + +:returns: the number of rows in the aggregate. + + This could be the total number of rows, the number of rows in the group, or the number of rows in the PM's subaggregate, depending on the context it was called. + +.. rubric:: Result Type + +.. c:function:: CalpontSystemCatalog::ColDataType getResultType() const; + +:returns: The result type. This will be set based on the CREATE FUNCTION SQL statement until over-ridden by setResultType(). + +.. c:function:: int32_t getScale() const; + +:returns The currently set scale. + + Scale is the number of digits to the right of the decimal point. This value is ignored if the type isn't decimal and is usually set to 0 for non-decimal types. + +.. c:function:: int32_t getPrecision() const; + +:returns: The currently set precision + + Precision is the total number of decimal digits both on the left and right of the decimal point. This value is ignored for non-decimal types and may be 0, -1 or based on the max digits of the integer type. + +.. c:function:: bool setResultType(CalpontSystemCatalog::ColDataType resultType); + +:param: The new result type for the UDA(n)F +:returns: true always + + If you wish to set the return type based on the input type, then use this function in your init() method. + +.. c:function:: bool setScale(int32_t scale); + +:param: The new scale for the return type of the UDA(n)F +:returns: true always + + Scale is the number of digits to the right of the decimal point. This value is ignored if the type isn't decimal and is usually set to 0 for non-decimal types. + +.. c:function:: bool setPrecision(int32_t precision); + +:param: The new precision for the return type of the UDA(n)F +:returns: true always + + Precision is the total number of decimal digits both on the left and right of the decimal point. This value is ignored for non-decimal types and may be 0, -1 or based on the max digits of the integer type. + +.. c:function:: int32_t getColWidth(); + +:returns: The current column width of the return type. + + For all types, get the return column width in bytes. Ex. INT will return 4. VARCHAR(255) will return 255 regardless of any contents. + +.. c:function:: bool setColWidth(int32_t colWidth); + +:param: The new column width for the return type of the UDA(n)F +:returns: true always + + For non-numric return types, set the return column width. This defaults to the the max length of the input data type. + +.. rubric:: system interruption + +.. c:function:: bool getInterrupted() const; + +:returns: true if any thread for this function set an error. + + If a method is known to take a while, call this periodically to see if something interupted the processing. If getInterrupted() returns true, then the executing method should clean up and exit. + +.. rubric:: User Data + +.. c:function:: void setUserDataSize(int bytes); + +:param bytes: The number of bytes to be reserved for working memory. + + Sets the size of instance specific memory for :ref:`simpledatamodel`. This value is ignored if using :ref:`complexdatamodel` unless you specifically use it. + +.. c:function:: UserData* getUserData(); + +:returns: A pointer to the current set of user data. + + Type cast to your user data structure if using :ref:`complexdatamodel`. This is the function to call in each of your methods to get the current working copy of your user data. + +.. rubric:: Window Frame + +All UDAnF need a default Window Frame. If none is set here, the default is UNBOUNDED PRECEDING to CURRENT ROW. + +It's possible to not allow the the WINDOW FRAME phrase in the UDAnF by setting the UDAF_WINDOWFRAME_REQUIRED and UDAF_WINDOWFRAME_ALLOWED both to false. However, Columnstore requires a Window Frame in order to process UDAnF. In this case, the default will be used for all calls. + +Possible values for start frame are: + +* WF_UNBOUNDED_PRECEDING +* WF_CURRENT_ROW +* WF_PRECEDING +* WF_FOLLOWING + +Possible values for end frame are: + +* WF_CURRENT_ROW +* WF_UNBOUNDED_FOLLOWING +* WF_PRECEDING +* WF_FOLLOWING + +If WF_PRECEEDING and/or WF_FOLLOWING, a start or end constant should be included to say how many preceeding or following is the default (the frame offset). + +Window Frames are not allowed to have reverse values. That is, the start frame must preceed the end frame. You can't set start = WF_FOLLOWING and end = WF_PRECEDDING. Results are undefined. + +.. c:function:: bool setDefaultWindowFrame(WF_FRAME defaultStartFrame, WF_FRAME defaultEndFrame, int32_t startConstant = 0, int32_t endConstant = 0); + +:param defaultStartFrame: An enum value from the list above. +:param defaultEndFrame: An enum value from the list above. +:param startConstant: An integer value representing the frame offset. This may be negative. Only used if start frame is one of WF_PRECEEDING or WF_FOLLOWING. +:param endConstant: An integer value representing the frame offset. This may be negative. Only used if start frame is one of WF_PRECEEDING or WF_FOLLOWING. + +.. c:function:: void getStartFrame(WF_FRAME& startFrame, int32_t& startConstant) const; + +:param startFrame (out): Returns the start frame as set by the function call in the query, or the default if the query doesn't include a WINDOW FRAME clause. + +:param startConstant (out): Returns the start frame offset. Only valid if startFrame is one of WF_PRECEEDING or WF_FOLLOWING. + +.. c:function:: void getEndFrame(WF_FRAME& endFrame, int32_t& endConstant) const; + +:param endFrame (out): Returns the end frame as set by the function call in the query, or the default if the query doesn't include a WINDOW FRAME clause. + +:param endConstant (out): Returns the end frame offset. Only valid if endFrame is one of WF_PRECEEDING or WF_FOLLOWING. + +.. rubric:: Deep Equivalence +.. c:function:: bool operator==(const mcsv1Context& c) const; +.. c:function:: bool operator!=(const mcsv1Context& c) const; + +.. rubric:: string operator +.. c:function:: const std::string toString() const; + +:returns: A string containing many of the values of the context in a human readable format for debugging purposes. + +.. rubric:: The name of the function +.. c:function:: void setName(std::string name); + +:param name: The name of the function. + + Setting the name of the function is optional. You can set the name in your init() method to be retrieved by other methods later. You may want to do this, for instance, if you want to use the UDA(n)F name in an error or log message. + +.. c:function:: const std::string& getName() const; + +:returns: The name of the function as set by setName(). + diff --git a/utils/udfsdk/docs/source/reference/mcsv1_UDAF.rst b/utils/udfsdk/docs/source/reference/mcsv1_UDAF.rst new file mode 100644 index 000000000..2c1513a4f --- /dev/null +++ b/utils/udfsdk/docs/source/reference/mcsv1_UDAF.rst @@ -0,0 +1,196 @@ +mcsv1_UDAF +========== + +class mcsv1_UDAF is the class from which you derive your UDA(n)F class. There are a number of methodss which you must implement, and a couple that you only need for certain purposes. + +All the methods except the constuctor and destructor take as parameters at least a pointer to the context object and return a status code. The context contains many useful tidbits of information, and most importantly, a copy of your userdata that you manipulate. + +The base class has no data members. It is designed to be only a container for your callback methods. In most cases, UDA(n)F implementations should also not have any data members. There is no guarantee that the instance called at any given time is the exact instance called at another time. Data will not persist from call to call. This object is not streamed, and there is no expectation by the framework that there is data in it. + +However, adding static const members makes sense. + +For UDAF (not Wndow Functions) Aggregation takes place in three stages: + +* Subaggregation on the PM. nextValue() +* Consolodation on the UM. subevaluate() +* Evaluation of the function on the UM. evaluate() + +For Window Functions, all aggregation occurs on the UM, and thus the subevaluate step is skipped. There is an optional dropValue() function that may be added. + +* Aggregation on the UM. nextValue() +* Optional aggregation on the UM using dropValue() +* Evaluation of the function on the UM. evaluate() + +There are a few ways that the system may call these methods for UDAnF. + +.. rubric:: The default way. + +For each Window movement: + +* call reset() to initialize a userData struct. +* call nextValue() for each value in the Window. +* call evaluate() + +.. rubric:: If the Frame is UNBOUNDED PRECEDING to CURRENT ROW: + +call reset() to initialize a userData struct. + +For each Window movement: + +* call nextValue() for each value entering the Window. +* call evaluate() + +.. rubric:: If dropValue() is defined: + +call reset() to initialize a userData struct. + +For each Window movement: + +* call dropValue() for each value leaving the Window. +* call nextValue() for each value entering the Window. +* call evaluate() + +return code +----------- + +Each function returns a ReturnCode (enum) it may be one of the following: + +* ERROR = 0, +* SUCCESS = 1, +* NOT_IMPLEMENTED = 2 // User UDA(n)F shouldn't return this + +Constructor +----------- + +.. c:function:: mcsv1_UDAF(); + + There are no base data members, so the constructor does nothing. + +Destructor +---------- + +.. c:function:: virtual ~mcsv1_UDAF(); + + The base destructor does nothing. + +Callback Methods +---------------- + +.. c:function:: ReturnCode init(mcsv1Context* context, COL_TYPES& colTypes); + +:param context: The context object for this call. + +:param colTypes: A list of the column types of the parameters. + + COL_TYPES is defined as:: + + typedef std::vector >COL_TYPES; + + In Columnstore 1.1, only one column is supported, so colTyoes will be of length one. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + Use init() to initialize flags and instance data. Init() will be called only once for any SQL query. + + This is where you should check for data type compatibility and set the return type if desired. + + If you see any issue with the data types or any other reason the function may fail, return ERROR, otherwise, return SUCCESS. + + All flags, return type, Window Frame, etc. set in the context object here will be propogated to any copies made and will be streamed to the various modules. You are guaranteed that these settings will be correct for each callback. + +.. c:function:: ReturnCode reset(mcsv1Context* context); + +:param context: The context object for this call. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + Reset the UDA(n)F for a new group, partition or, in some cases, new Window Frame. Do not free any memory directly allocated by createUserData(). The SDK Framework owns that memory and will handle that. However, Empty any user defined containers and free memory you allocated in other callback methods. Use this opportunity to reset any variables in your user data needed for the next aggregation. + + Use context->getUserData() and type cast it to your UserData type or Simple Data Model stuct. + +.. c:function:: ReturnCode nextValue(mcsv1Context* context, std::vector& valsIn); + +:param context: The context object for this call + +:param valsIn: a vector representing the values to be added for each parameter for this row. + + In Columnstore 1.1, this will be a vector of length one. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + Use context->getUserData() and type cast it to your UserData type or Simple Data Model stuct. + + nextValue() is called for each Window movement that passes the WHERE and HAVING clauses. The context's UserData will contain values that have been sub-aggregated to this point for the group, partition or Window Frame. nextValue is called on the PM for aggregation and on the UM for Window Functions. + + When used in an aggregate, the function may not rely on order or completeness since the sub-aggregation is going on at the PM, it only has access to the data stored on the PM's dbroots. + + When used as a analytic function (Window Function), nextValue is call for each Window movement in the Window. If dropValue is defined, then it may be called for every value leaving the Window, and nextValue called for each new value entering the Window. + + Since this is called for every row, it is important that this method be efficient. + +.. c:function:: ReturnCode subEvaluate(mcsv1Context* context, const UserData* userDataIn); + +:param context: The context object for this call + +:param userDataIn: A UserData struct representing the sub-aggregation + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + subEvaluate() is the middle stage of aggregation and runs on the UM. It should take the sub-aggregations from the PM's as filled in by nextValue(), and finish the aggregation. + + The userData struct in context will be newly initialized for the first call to subEvaluate for each GROUP BY. userDataIn will have the final values as set by nextValue() for a given PM and GROUP BY. + + Each call to subEvaluate should aggregate the values from userDataIn into the context's UserData struct. + +.. c:function:: ReturnCode evaluate(mcsv1Context* context, static_any::any& valOut); + +:param context: The context object for this call + +:param valOut [out]: The final value for this GROUP or WINDOW. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + evaluate() is the final stage of aggregation for all User Define Aggregate or Analytic Functions -- UDA(n)F. + + For aggregate (UDAF) use, the context's UserData struct will have the values as set by the last call to subEvaluate for a specific GROUP BY. + + For analytic use (UDAnF) the context's UserData struct will have the values as set by the latest call to nextValue() for the Window. + + Set your aggregated value into valOut. The type you set should be compatible with the type defined in the context's result type. The framework will do it's best to do any conversions if required. + +.. c:function:: ReturnCode dropValue(mcsv1Context* context, std::vector& valsDropped); + +:param context: The context object for this call + +:param valsDropped: a vector representing the values to be dropped for each parameter for this row. + + In Columnstore 1.1, this will be a vector of length one. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + If dropValue() is defined, it will optimize most calls as an analytic function. If your UDAnF will always be called with a Window Frame of UNBOUNDED PRECEDING to CURRENT ROW, then dropValue will never be called. For other Frames, dropValue can speed things up. There are cases where dropValue makes no sense. If you can't undo what nextValue() does, then dropValue won't work. + + dropValue() should perform the reverse of the actions taken by nextValue() for each Window movement. + + For example, for an AVG function:: + + nextValue: + Add the value to accumulator + increment row count + + dropValue: + Subtract the value from accumulator + decrement row count + +.. c:function:: ReturnCode createUserData(UserData*& userdata, int32_t& length); + +:param userData [out]: A pointer to be allocated by the function. + +:param length [out]: The length of the data allocated. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + See the chapter on :ref:`complexdatamodel` for more information on how to use this Method. + + New a UserData derived structure and return a pointer to it. Set length to the base length of the structure. + diff --git a/utils/udfsdk/docs/source/usage/headerfile.rst b/utils/udfsdk/docs/source/usage/headerfile.rst new file mode 100644 index 000000000..a24a5b344 --- /dev/null +++ b/utils/udfsdk/docs/source/usage/headerfile.rst @@ -0,0 +1,37 @@ +Header file +=========== + +Usually, each UDA(n)F function will have one .h and one .cpp file plus code for the mariadb UDAF plugin which may or may not be in a separate file. It is acceptable to put a set of related functions in the same files or use separate files for each. + +The easiest way to create these files is to copy them an example closest to the type of function you intend to create. + +Your header file must have a class defined that will implement your function. This class must be derived from mcsv1_UDAF and be in the mcsv1sdk namespace. The following examples use the "allnull" UDAF. + +First you must include at least the following: + +.. literalinclude:: ../../../allnull.h + :lines: 71-72 + :language: cpp + +Other headers as needed. Then:: + + namespace mcsv1sdk + { + +Next you must create your class that will implement the UDAF. You must have a constructor, virtual destructor and all the methods that are declared pure in the base class mcsv1_UDAF. There are also methods that have base class implementations. These you may extended as needed. Other methods may be added if you feel a need for helpers. + +allnull uses the Simple Data Model. See the Complex Data Model chapter to see how that is used. See the MEDIAN example to see the dropValue() usage. + +.. literalinclude:: ../../../allnull.h + :lines: 94-218 + :language: cpp + + + + + + + + + + diff --git a/utils/udfsdk/docs/source/usage/index.rst b/utils/udfsdk/docs/source/usage/index.rst new file mode 100644 index 000000000..bd5cff529 --- /dev/null +++ b/utils/udfsdk/docs/source/usage/index.rst @@ -0,0 +1,11 @@ +Using mcsv1_udaf +================ + +.. toctree:: + :maxdepth: 3 + + introduction + memoryallocation + headerfile + sourcefile + diff --git a/utils/udfsdk/docs/source/usage/introduction.rst b/utils/udfsdk/docs/source/usage/introduction.rst new file mode 100644 index 000000000..9cff92bb7 --- /dev/null +++ b/utils/udfsdk/docs/source/usage/introduction.rst @@ -0,0 +1,22 @@ +mcsv1_udaf Introduction +======================= + +mcsv1_udaf is a C++ API for writing User Defined Aggregate Functions (UDAF) and User Defined Analytic Functions (UDAnF) for the MariaDB Columstore engine. + +In Columnstore 1.1.0, functions written using this API must be compiled into the udfsdk and udf_mysql libraries of the Columnstore code branch. + +The API has a number of features. The general theme is, there is a class that represents the function, there is a context under which the function operates, and there is a data store for intermediate values. + +The steps required to create a function are: + +* Decide on memory allocation scheme. +* Create a header file for your function. +* Create a source file for your function. +* Implement mariadb udaf api code. +* Add the function to UDAFMap in mcsv1_udaf.cpp +* Add the function to CMakeLists.txt in ./utils/udfsdk +* Compile udfsdk. +* Copy the compiled libraries to the working directories. + +In 1.1.0, Columnstore does not have a plugin framework, so the functions have to be compiled into the libraries that Columnstore already loads. + diff --git a/utils/udfsdk/docs/source/usage/memoryallocation.rst b/utils/udfsdk/docs/source/usage/memoryallocation.rst new file mode 100644 index 000000000..c98a314e2 --- /dev/null +++ b/utils/udfsdk/docs/source/usage/memoryallocation.rst @@ -0,0 +1,96 @@ +Memory allocation and usage +=========================== + +Memory for the function is allocated and owned by the engine. In a distributed system like columnstore, memory must be allocated on each module. While it would be possible to let the API control memory, it's far simpler and safer to let the engine handle it. + +There are two memory models available -- simple and complex. + +Memory is allocated via callbacks to the mcsv1_UDAF::createUserData() method whose default implementation implements the Simple Data Model. If using the simple model, all you need to do is tell the system how much memory you need allocated. Everything else will be taken care of. + +.. _simpledatamodel: + +The Simple Data Model +--------------------- + +Many UDAF can be accomplished with a fixed memory model. For instance, an AVG function just needs an accumulator and a counter. This can be handled by two data items. + +For the Simple Data Model, define a structure that has all the elements you need to do the function. Then, in your init() method, set the memory size you need. Each callback will have a pointer to a context whose data member is a pointer to the memory you need. Just cast to the structure you defined. allnull defines this structure: + +.. literalinclude:: ../../../allnull.cpp + :lines: 22-26 + :language: cpp + +In the init method, it tells the framework how much memory it needs using the context's setUserDataSize() method: + +.. literalinclude:: ../../../allnull.cpp + :lines: 32 + :language: cpp + + +and uses it like this in the reset method: + +.. literalinclude:: ../../../allnull.cpp + :lines: 50-56 + :language: cpp + +Notice that the memory is already allocated. All you do is typecast it. + +.. _complexdatamodel: + +The Complex Data Model +---------------------- + +There are functions where a fixed size memory chunk isn't sufficient, We need variable size data, containers of things, etc. The columnstore UDAF API supports these needs using the Complex Data Model. + +This consists of the createUserData() method that must be over ridden and the UserData struct that must be extended to create your data class. + +.. rubric:: struct UserData + +Let's take a look at the base user data structure defined in mcsv1_udaf.h: + +.. literalinclude:: ../../../mcsv1_udaf.h + :lines: 126-168 + :language: cpp + +There are two constructors listed. The second one taking a size is used by :ref:`simpledatamodel`. + +Next you'll notice the serialize and unserialize methods. These must be defined for your function. The default just bit copies the contents of the data member, which is where memory for :ref:`simpledatamodel` is stored. + +To create a Complex Data Model, derive a class from UserData and create instances of the new class in your function's createUserData() method. + +Memory is allocated on each module as we've already discussed. In a Complex Data Model, the engine has no idea how much memory needs to be passed from PM to UM after the intermediate steps of the aggregation have been accomplished. It handles this by calling streaming functions on the user data structure. This is what the serialize and unserialize methods are for. + +It is very important that these two methods reflect each other exactly. If they don't, you will have alignment issues that most likely will lead to a SEGV signal 11. + +In the MEDIAN UDAF, it works like this: + +.. literalinclude:: ../../../median.h + :lines: 80-97 + :language: cpp + +Notice that the data is to be stored in a std::map container. + +The serialize method leverages Columnstore's ByteStream class (in namespace messageqcpp). This is not optional. The serialize and unserialize methods are each passed an reference to a ByteStream. The framework expects the data to be streamed into the ByteStream by serialize and streamed back into your data struct by unserialize. See the chapter on ByteStream for more information. + +For MEDIAN, serialize() iterates over the set and streams the values to the ByteStream and unserialze unstreams them back into the set: + +.. literalinclude:: ../../../median.cpp + :lines: 290-305 + :language: cpp + +.. rubric:: createUserData() + +The createUserData() method() has two parameters, a pointer reference to userData, and a length. Both of these are [OUT] parameters. + +The userData parameter is to be set to an instance of your new data structure. + +The length field isn't very important in the Complex Data Model, as the data structure us usually of variable size. + +This function may be called many times from different modules. Do not expect your data to be always contained in the same physical space or consolodated together. For instance, each PM will allocate memory separately for each GROUP in a GROUP BY clause. The UM will also allocate at least one instance to handle final aggregation. + +The implementation of the createUserData() method() in MEDIAN: + +.. literalinclude:: ../../../median.cpp + :lines: 283-288 + :language: cpp + diff --git a/utils/udfsdk/docs/source/usage/sourcefile.rst b/utils/udfsdk/docs/source/usage/sourcefile.rst new file mode 100644 index 000000000..d3995c55e --- /dev/null +++ b/utils/udfsdk/docs/source/usage/sourcefile.rst @@ -0,0 +1,261 @@ +Source file +=========== + +Usually, each UDA(n)F function will have just one .cpp. Be sure to write your header file first. It's much easier to implent the various parts if you have a template to work from. + +The easiest way to create these files is to copy them an example closest to the type of function you intend to create. + +You need a data structure to hold your aggregate values. You can either use :ref:`simpledatamodel`, or :ref:`complexdatamodel`. + +You may only need a few accunulators and counters. These can be represented as a fixed size data structure. For these needs, you may choose :ref:`simpledatamodel`. Here's a struct for a possible AVG function:: + + struct AVGdata + { + uint64_t total; + uint64_t count; + }; + +If you have a more complex data structure that may have varying size, you must use :ref:`complexdatamodel`. This should be defined in the header. Here's a struct for MEDIAN example from median.h: + +.. literalinclude:: ../../../median.h + :lines: 84-97 + :language: cpp + +In each of the functions that have a context parameter, you should type cast the data member of context's UserData member:: + + struct AVGdata* data = (struct allnull_data*)context->getUserData()->data; + +Or, if using the :ref:`complexdatamodel`, type cast the UserData to your UserData derived struct:: + + MedianData* data = static_cast(context->getUserData()); + +init() +------ + +.. c:function:: ReturnCode init(mcsv1Context* context, COL_TYPES& colTypes); + +:param context: The context object for this call. + +:param colTypes: A list of the column types of the parameters. + + COL_TYPES is defined as:: + + typedef std::vector >COL_TYPES; + + see :ref:`ColDataTypes `. In Columnstore 1.1, only one column is supported, so colTyoes will be of length one. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + +The init() method is where you sanity check the input, set the output type and set any run flags for this instance. init() is called one time from the mysqld process. All settings you do here are propagated through the system. + +init() is the exception to type casting the UserData member of context. UserData has not been created when init() is called, so you shouldn't use it here. + +.. rubric:: Set User Data Size + +If you're using :ref:`simpledatamodel`, you need to set the size of the structure:: + + context->setUserDataSize(sizeof(allnull_data)); + +.. rubric:: Check parameter count and type + +Each function expects a certain number of columns to entered as parameters in the SQL query. For columnstore 1.1, the number of parameters is limited to one. + +colTypes is a vector of each parameter name and type. The name is the colum name from the SQL query. You can use this information to sanity check for compatible type(s) and also to modify your functions behavior based on type. To do this, add members to your data struct to be tested in the other Methods. Set these members based on colDataTypes (:ref:`ColDataTypes `). + +:: + + if (colTypes.size() < 1) + { + // The error message will be prepended with + // "The storage engine for the table doesn't support " + context->setErrorMessage("allnull() with 0 arguments"); + return mcsv1_UDAF::ERROR; + } + +.. rubric:: Set the ResultType + +When you create your function using the SQL CREATE FUNCTION command, you must include a result type in the command. However, you're not completely limited by that decision. You may choose to return a different type based on any number of factors, including the colTypes. setResultType accepts any of the CalpontSystemCatalog::ColType enum values(:ref:`ColDataTypes `). + +:: + + context->setResultType(CalpontSystemCatalog::TINYINT); + +.. rubric:: Set width and scale + +If you have secial requirements, especially if you might be dealing with decimal types:: + + context->setColWidth(8); + context->setScale(context->getScale()*2); + context->setPrecision(19); + + +.. rubric:: Set runflags + +There are a number of run flags that you can set. Most are for use as an analytic function (Window Function), but a useful one for all functions is UDAF_IGNORE_NULLS. see :ref:`Run Flags ` for a complete list:: + + context->setRunFlag(mcsv1sdk::UDAF_IGNORE_NULLS); + +reset() +------- + +.. c:function:: ReturnCode reset(mcsv1Context* context); + +:param context: The context object for this call. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + +The reset() method initializes the context for a new aggregation or sub-aggregation. + +Then initialize the data in whatever way you need to:: + + data->mData.clear(); + +This function may be called multiple times from both the UM and the PM. Make no assumptions about useful data in UserData from call to call. + +nextValue() +----------- + +.. c:function:: ReturnCode nextValue(mcsv1Context* context, std::vector& valsIn); + +:param context: The context object for this call + +:param valsIn: a vector representing the values to be added for each parameter for this row. + + In Columnstore 1.1, this will be a vector of length one. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + +nextValue() is called from the PM for aggregate usage and the UM for Analytic usage. + +valsIn contains a vector of all the parameters from the function call in the SQL query (In Columndtore 1.1, this will always contain exactly one entry). + +Depending on your function, you may wish to be able to handle many different types of input. A good way to handle this is to have a series of if..else..if statements comparing the input type and dealing with each separately. For instace, if you want to handle multiple numeric types, you might use:: + + static_any::any& valIn = valsDropped[0].columnData; + AVGData& data = static_cast(context->getUserData())->mData; + int64_t val = 0; + + if (valIn.empty()) + { + return mcsv1_UDAF::SUCCESS; // Ought not happen when UDAF_IGNORE_NULLS is on. + } + + if (valIn.compatible(charTypeId)) + { + val = valIn.cast(); + } + else if (valIn.compatible(scharTypeId)) + { + val = valIn.cast(); + } + else if (valIn.compatible(shortTypeId)) + { + val = valIn.cast(); + } + . + . + . + +Once you've gotten your data in a format you like, then do your aggregation. For AVG, you might see:: + + data.total = val; + ++data.count; + +subEvaluate +----------- + +.. c:function:: ReturnCode subEvaluate(mcsv1Context* context, const UserData* userDataIn); + +:param context: The context object for this call + +:param userDataIn: A UserData struct representing the sub-aggregation + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + +subEvaluate() is called on the UM for the consolidation of the subaggregations from the PM. The sub-aggregate from the PM is in userDataIn and the result is to be placed into the UserData struct of context. In this case, you need to type cast userDataIn in a similar fashion as you do the context's UserData struct. + +For AVG, you might see:: + + struct AVGdata* outData = (struct AVGdata*)context->getUserData()->data; + struct AVGdata* inData = (struct AVGdata*)userDataIn->data; + outData->total += inData->total; + outData->count += inData->count; + return mcsv1_UDAF::SUCCESS; + +evaluate +-------- + +.. c:function:: ReturnCode evaluate(mcsv1Context* context, static_any::any& valOut); + +:param context: The context object for this call + +:param valOut [out]: The final value for this GROUP or WINDOW. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + +evaluate() is where you do your final calculations. It's pretty straight forward and is seldom different for UDAF (aggregation) or UDAnF (analytic). + +For AVG, you might see:: + + int64_t avg; + struct AVGdata* data = (struct AVGdata*)context->getUserData()->data; + avg = data->total / data.count; + valOut = avg; + return mcsv1_UDAF::SUCCESS; + +dropValue +--------- + +.. c:function:: ReturnCode dropValue(mcsv1Context* context, std::vector& valsDropped); + +:param context: The context object for this call + +:param valsDropped: a vector representing the values to be dropped for each parameter for this row. + +dropValue is an optional method for optimizing UDAnF (Analytic Functions). When used as an aggregate UDAF, dropValue isn't called. + +As a Window Moves, some values come into scope and some values leave scope. When values leave scope, dropValue is called so that we don't have to recalculate the whole Window. We just need to undo what was done in nextValue for the dropped entries. + +Like nextValue, your function may be able to handle a whole range of data types: For AVG, you might have:: + + static_any::any& valIn = valsDropped[0].columnData; + AVGData& data = static_cast(context->getUserData())->mData; + int64_t val = 0; + + if (valIn.empty()) + { + return mcsv1_UDAF::SUCCESS; // Ought not happen when UDAF_IGNORE_NULLS is on. + } + + if (valIn.compatible(charTypeId)) + { + val = valIn.cast(); + } + else if (valIn.compatible(scharTypeId)) + { + val = valIn.cast(); + } + else if (valIn.compatible(shortTypeId)) + { + val = valIn.cast(); + } + . + . + . + + data.total -= val; + --data.count; + + return mcsv1_UDAF::SUCCESS; + +.. c:function:: ReturnCode createUserData(UserData*& userdata, int32_t& length); + +:param userData [out]: A pointer to be allocated by the function. + +:param length [out]: The length of the data allocated. + +:returns: ReturnCode::ERROR or ReturnCode::SUCCESS + + See the chapter on :ref:`complexdatamodel` for more information on how to use this Method. + +