changeset 0:7cea966b6829

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 04 Jul 2018 08:16:29 +0200
parents
children d17b2631bb67
files AUTHORS COPYING Framework/Common/BinaryStringValue.cpp Framework/Common/BinaryStringValue.h Framework/Common/DatabaseManager.cpp Framework/Common/DatabaseManager.h Framework/Common/DatabasesEnumerations.h Framework/Common/Dictionary.cpp Framework/Common/Dictionary.h Framework/Common/FileValue.cpp Framework/Common/FileValue.h Framework/Common/GenericFormatter.cpp Framework/Common/GenericFormatter.h Framework/Common/IDatabase.h Framework/Common/IDatabaseFactory.h Framework/Common/IPrecompiledStatement.h Framework/Common/IResult.h Framework/Common/ITransaction.h Framework/Common/IValue.h Framework/Common/Integer64Value.cpp Framework/Common/Integer64Value.h Framework/Common/NullValue.cpp Framework/Common/NullValue.h Framework/Common/Query.cpp Framework/Common/Query.h Framework/Common/ResultBase.cpp Framework/Common/ResultBase.h Framework/Common/StatementLocation.cpp Framework/Common/StatementLocation.h Framework/Common/Utf8StringValue.cpp Framework/Common/Utf8StringValue.h Framework/MySQL/MySQLDatabase.cpp Framework/MySQL/MySQLDatabase.h Framework/MySQL/MySQLParameters.cpp Framework/MySQL/MySQLParameters.h Framework/MySQL/MySQLResult.cpp Framework/MySQL/MySQLResult.h Framework/MySQL/MySQLStatement.cpp Framework/MySQL/MySQLStatement.h Framework/MySQL/MySQLTransaction.cpp Framework/MySQL/MySQLTransaction.h Framework/Plugins/GlobalProperties.cpp Framework/Plugins/GlobalProperties.h Framework/Plugins/IndexBackend.cpp Framework/Plugins/IndexBackend.h Framework/Plugins/IndexUnitTests.h Framework/Plugins/OrthancCppDatabasePlugin.h Framework/PostgreSQL/PostgreSQLDatabase.cpp Framework/PostgreSQL/PostgreSQLDatabase.h Framework/PostgreSQL/PostgreSQLLargeObject.cpp Framework/PostgreSQL/PostgreSQLLargeObject.h Framework/PostgreSQL/PostgreSQLParameters.cpp Framework/PostgreSQL/PostgreSQLParameters.h Framework/PostgreSQL/PostgreSQLResult.cpp Framework/PostgreSQL/PostgreSQLResult.h Framework/PostgreSQL/PostgreSQLStatement.cpp Framework/PostgreSQL/PostgreSQLStatement.h Framework/PostgreSQL/PostgreSQLTransaction.cpp Framework/PostgreSQL/PostgreSQLTransaction.h Framework/SQLite/SQLiteDatabase.cpp Framework/SQLite/SQLiteDatabase.h Framework/SQLite/SQLiteResult.cpp Framework/SQLite/SQLiteResult.h Framework/SQLite/SQLiteStatement.cpp Framework/SQLite/SQLiteStatement.h Framework/SQLite/SQLiteTransaction.cpp Framework/SQLite/SQLiteTransaction.h MySQL/CMakeLists.txt MySQL/NEWS MySQL/Plugins/IndexPlugin.cpp MySQL/Plugins/MySQLIndex.cpp MySQL/Plugins/MySQLIndex.h MySQL/Plugins/PrepareIndex.sql MySQL/UnitTests/UnitTestsMain.cpp PostgreSQL/CMakeLists.txt PostgreSQL/NEWS PostgreSQL/Plugins/IndexPlugin.cpp PostgreSQL/Plugins/PostgreSQLIndex.cpp PostgreSQL/Plugins/PostgreSQLIndex.h PostgreSQL/Plugins/PrepareIndex.sql PostgreSQL/UnitTests/PostgreSQLTests.cpp PostgreSQL/UnitTests/UnitTestsMain.cpp README Resources/CMake/DatabasesFrameworkConfiguration.cmake Resources/CMake/DatabasesFrameworkParameters.cmake Resources/CMake/DatabasesPluginConfiguration.cmake Resources/CMake/DatabasesPluginParameters.cmake Resources/CMake/FindPostgreSQL.cmake Resources/CMake/MariaDBConfiguration.cmake Resources/CMake/PostgreSQLConfiguration.cmake Resources/MariaDB/mariadb-connector-c-3.0.5.patch Resources/Orthanc/DownloadOrthancFramework.cmake Resources/Orthanc/LinuxStandardBaseToolchain.cmake Resources/Orthanc/MinGW-W64-Toolchain32.cmake Resources/Orthanc/MinGW-W64-Toolchain64.cmake Resources/Orthanc/MinGWToolchain.cmake Resources/Orthanc/Sdk-0.9.5/orthanc/OrthancCDatabasePlugin.h Resources/Orthanc/Sdk-0.9.5/orthanc/OrthancCPlugin.h Resources/PostgreSQL/PrepareCMakeConfigurationFile.py Resources/PostgreSQL/c_flexmember.c Resources/PostgreSQL/func_accept_args.cmake Resources/PostgreSQL/pg_config_ext.h Resources/PostgreSQL/printf_archetype.c Resources/SyncOrthancFolder.py SQLite/CMakeLists.txt SQLite/NEWS SQLite/Plugins/IndexPlugin.cpp SQLite/Plugins/PrepareIndex.sql SQLite/Plugins/SQLiteIndex.cpp SQLite/Plugins/SQLiteIndex.h SQLite/UnitTests/UnitTestsMain.cpp
diffstat 111 files changed, 21281 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,20 @@
+Database plugins for Orthanc
+============================
+
+
+Authors
+-------
+
+* Sebastien Jodogne <s.jodogne@gmail.com>
+
+  Overall design and lead developer.
+
+* Department of Medical Physics
+  University Hospital of Liege
+  4000 Liege
+  Belgium
+
+* Osimis S.A. <info@osimis.io>
+  Rue des Chasseurs Ardennais 3
+  4031 Liege 
+  Belgium
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/BinaryStringValue.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "BinaryStringValue.h"
+
+#include "FileValue.h"
+#include "NullValue.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  IValue* BinaryStringValue::Convert(ValueType target) const
+  {
+    switch (target)
+    {
+      case ValueType_File:
+        return new FileValue(content_);
+
+      case ValueType_Null:
+        return new NullValue;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+  }
+
+
+  std::string BinaryStringValue::Format() const
+  {
+    return "(binary - " + boost::lexical_cast<std::string>(content_.size()) + " bytes)";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/BinaryStringValue.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  class BinaryStringValue : public IValue
+  {
+  private:
+    std::string  content_;
+
+  public:
+    explicit BinaryStringValue()
+    {
+    }
+
+    explicit BinaryStringValue(const std::string& content) :
+      content_(content)
+    {
+    }
+
+    BinaryStringValue(const void* content,
+                      size_t size)
+    {
+      content_.assign(reinterpret_cast<const char*>(content), size);
+    }
+
+    std::string& GetContent()
+    {
+      return content_;
+    }
+
+    const std::string& GetContent() const
+    {
+      return content_;
+    }
+
+    const void* GetBuffer() const
+    {
+      return (content_.empty() ? NULL : content_.c_str());
+    }
+
+    size_t GetSize() const
+    {
+      return content_.size();
+    }
+
+    virtual ValueType GetType() const
+    {
+      return ValueType_BinaryString;
+    }
+    
+    virtual IValue* Convert(ValueType target) const;
+
+    virtual std::string Format() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/DatabaseManager.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,439 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DatabaseManager.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/thread.hpp>
+
+namespace OrthancDatabases
+{
+  IDatabase& DatabaseManager::GetDatabase()
+  {
+    static const unsigned int MAX_CONNECTION_ATTEMPTS = 10;   // TODO: Parameter
+
+    unsigned int count = 0;
+      
+    while (database_.get() == NULL)
+    {
+      transaction_.reset(NULL);
+
+      try
+      {
+        database_.reset(factory_->Open());
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        if (e.GetErrorCode() == Orthanc::ErrorCode_DatabaseUnavailable)
+        {
+          count ++;
+
+          if (count <= MAX_CONNECTION_ATTEMPTS)
+          {
+            LOG(WARNING) << "Database is currently unavailable, retrying...";
+            boost::this_thread::sleep(boost::posix_time::seconds(1));
+            continue;
+          }
+          else
+          {
+            LOG(ERROR) << "Timeout when connecting to the database, giving up";
+          }
+        }
+
+        throw;
+      }
+    }
+
+    if (database_.get() == NULL ||
+        database_->GetDialect() != dialect_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return *database_;
+    }
+  }
+
+
+  void DatabaseManager::Close()
+  {
+    LOG(TRACE) << "Closing the connection to the database";
+
+    // Rollback active transaction, if any
+    transaction_.reset(NULL);
+
+    // Delete all the cached statements (must occur before closing
+    // the database)
+    for (CachedStatements::iterator it = cachedStatements_.begin();
+         it != cachedStatements_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+
+    cachedStatements_.clear();
+
+    // Close the database
+    database_.reset(NULL);
+
+    LOG(TRACE) << "Connection to the database is closed";
+  }
+
+    
+  void DatabaseManager::CloseIfUnavailable(Orthanc::ErrorCode e)
+  {
+    if (e != Orthanc::ErrorCode_Success)
+    {
+      transaction_.reset(NULL);
+    }
+
+    if (e == Orthanc::ErrorCode_DatabaseUnavailable)
+    {
+      LOG(ERROR) << "The database is not available, closing the connection";
+      Close();
+    }
+  }
+
+
+  IPrecompiledStatement* DatabaseManager::LookupCachedStatement(const StatementLocation& location) const
+  {
+    CachedStatements::const_iterator found = cachedStatements_.find(location);
+
+    if (found == cachedStatements_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return found->second;
+    }
+  }
+
+    
+  IPrecompiledStatement& DatabaseManager::CacheStatement(const StatementLocation& location,
+                                                         const Query& query)
+  {
+    LOG(TRACE) << "Caching statement from " << location.GetFile() << ":" << location.GetLine();
+      
+    std::auto_ptr<IPrecompiledStatement> statement(GetDatabase().Compile(query));
+      
+    IPrecompiledStatement* tmp = statement.get();
+    if (tmp == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    assert(cachedStatements_.find(location) == cachedStatements_.end());
+    cachedStatements_[location] = statement.release();
+
+    return *tmp;
+  }
+
+    
+  ITransaction& DatabaseManager::GetTransaction()
+  {
+    if (transaction_.get() == NULL)
+    {
+      LOG(TRACE) << "Automatically creating a database transaction";
+
+      try
+      {
+        transaction_.reset(GetDatabase().CreateTransaction());
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        CloseIfUnavailable(e.GetErrorCode());
+        throw;
+      }
+    }
+
+    assert(transaction_.get() != NULL);
+    return *transaction_;
+  }
+
+    
+  DatabaseManager::DatabaseManager(IDatabaseFactory* factory) :  // Takes ownership
+    factory_(factory)
+  {
+    if (factory == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    dialect_ = factory->GetDialect();
+  }
+
+  
+  void DatabaseManager::StartTransaction()
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+
+    try
+    {
+      if (transaction_.get() != NULL)
+      {
+#if 0
+        // TODO: This should be the right implementation
+        if (transaction_->IsReadOnly())
+        {
+          LOG(TRACE) << "Rollback of an uncommitted read-only transaction to start another transaction";
+          transaction_->Rollback();
+          transaction_.reset(NULL);
+        }
+        else
+        {
+          LOG(ERROR) << "Cannot rollback an uncommitted write transaction to start another transaction";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+        }
+#else
+        LOG(INFO) << "Committing an uncommitted transaction to start another transaction";
+        transaction_->Commit();
+        transaction_.reset(NULL);
+#endif
+      }
+
+      transaction_.reset(GetDatabase().CreateTransaction());
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+  
+
+  void DatabaseManager::CommitTransaction()
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+
+    if (transaction_.get() == NULL)
+    {
+      LOG(ERROR) << "Cannot commit a non-existing transaction";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      try
+      {
+        transaction_->Commit();
+        transaction_.reset(NULL);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        CloseIfUnavailable(e.GetErrorCode());
+        throw;
+      }
+    }
+  }
+
+
+  void DatabaseManager::RollbackTransaction()
+  {
+    boost::recursive_mutex::scoped_lock lock(mutex_);
+
+    if (transaction_.get() == NULL)
+    {
+      LOG(ERROR) << "Cannot rollback a non-existing transaction";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      try
+      {
+        transaction_->Rollback();
+        transaction_.reset(NULL);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        CloseIfUnavailable(e.GetErrorCode());
+        throw;
+      }
+    }
+  }
+
+
+  IResult& DatabaseManager::CachedStatement::GetResult() const
+  {
+    if (result_.get() == NULL)
+    {
+      LOG(ERROR) << "Accessing the results of a statement without having executed it";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return *result_;
+  }
+
+
+  DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location,
+                                                    DatabaseManager& manager,
+                                                    const char* sql) :
+    lock_(manager.mutex_),
+    manager_(manager),
+    location_(location),
+    database_(manager.GetDatabase()),
+    transaction_(manager.GetTransaction())
+  {
+    statement_ = manager_.LookupCachedStatement(location);
+
+    if (statement_ == NULL)
+    {
+      query_.reset(new Query(sql));
+    }
+    else
+    {
+      LOG(TRACE) << "Reusing cached statement from "
+                 << location.GetFile() << ":" << location.GetLine();
+    }
+  }
+
+      
+  void DatabaseManager::CachedStatement::SetReadOnly(bool readOnly)
+  {
+    if (query_.get() != NULL)
+    {
+      query_->SetReadOnly(readOnly);
+    }
+  }
+
+
+  void DatabaseManager::CachedStatement::SetParameterType(const std::string& parameter,
+                                                          ValueType type)
+  {
+    if (query_.get() != NULL)
+    {
+      query_->SetType(parameter, type);
+    }
+  }
+      
+      
+  void DatabaseManager::CachedStatement::Execute()
+  {
+    Dictionary parameters;
+    Execute(parameters);
+  }
+
+
+  void DatabaseManager::CachedStatement::Execute(const Dictionary& parameters)
+  {
+    if (result_.get() != NULL)
+    {
+      LOG(ERROR) << "Cannot execute twice a statement";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    try
+    {
+      if (query_.get() != NULL)
+      {
+        // Register the newly-created statement
+        assert(statement_ == NULL);
+        statement_ = &manager_.CacheStatement(location_, *query_);
+        query_.reset(NULL);
+      }
+        
+      assert(statement_ != NULL);
+      result_.reset(transaction_.Execute(*statement_, parameters));
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+
+
+  bool DatabaseManager::CachedStatement::IsDone() const
+  {
+    try
+    {
+      return GetResult().IsDone();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+
+
+  void DatabaseManager::CachedStatement::Next()
+  {
+    try
+    {
+      GetResult().Next();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+
+
+  size_t DatabaseManager::CachedStatement::GetResultFieldsCount() const
+  {
+    try
+    {
+      return GetResult().GetFieldsCount();
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+
+
+  void DatabaseManager::CachedStatement::SetResultFieldType(size_t field,
+                                                            ValueType type)
+  {
+    try
+    {
+      if (!GetResult().IsDone())
+      {
+        GetResult().SetExpectedType(field, type);
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+
+
+  const IValue& DatabaseManager::CachedStatement::GetResultField(size_t index) const
+  {
+    try
+    {
+      return GetResult().GetField(index);
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      manager_.CloseIfUnavailable(e.GetErrorCode());
+      throw;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/DatabaseManager.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,128 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDatabaseFactory.h"
+#include "StatementLocation.h"
+
+#include <Core/Enumerations.h>
+
+#include <boost/thread/recursive_mutex.hpp>
+#include <memory>
+
+namespace OrthancDatabases
+{
+  class DatabaseManager : public boost::noncopyable
+  {
+  private:
+    typedef std::map<StatementLocation, IPrecompiledStatement*>  CachedStatements;
+
+    boost::recursive_mutex           mutex_;
+    std::auto_ptr<IDatabaseFactory>  factory_;
+    std::auto_ptr<IDatabase>         database_;
+    std::auto_ptr<ITransaction>      transaction_;
+    CachedStatements                 cachedStatements_;
+    Dialect                          dialect_;
+
+    IDatabase& GetDatabase();
+
+    void CloseIfUnavailable(Orthanc::ErrorCode e);
+
+    IPrecompiledStatement* LookupCachedStatement(const StatementLocation& location) const;
+
+    IPrecompiledStatement& CacheStatement(const StatementLocation& location,
+                                          const Query& query);
+
+    ITransaction& GetTransaction();
+    
+  public:
+    DatabaseManager(IDatabaseFactory* factory);  // Takes ownership
+    
+    ~DatabaseManager()
+    {
+      Close();
+    }
+
+    Dialect GetDialect() const
+    {
+      return dialect_;
+    }
+
+    void Open()
+    {
+      GetDatabase();
+    }
+
+    void Close();
+    
+    void StartTransaction();
+
+    void CommitTransaction();
+    
+    void RollbackTransaction();
+
+    class CachedStatement : public boost::noncopyable
+    {
+    private:
+      boost::recursive_mutex::scoped_lock  lock_;
+      DatabaseManager&                     manager_;
+      StatementLocation                    location_;
+      IDatabase&                           database_;
+      ITransaction&                        transaction_;
+      IPrecompiledStatement*               statement_;
+      std::auto_ptr<Query>                 query_;
+      std::auto_ptr<IResult>               result_;
+
+      IResult& GetResult() const;
+
+    public:
+      CachedStatement(const StatementLocation& location,
+                      DatabaseManager& manager,
+                      const char* sql);
+
+      void SetReadOnly(bool readOnly);
+
+      void SetParameterType(const std::string& parameter,
+                            ValueType type);
+      
+      void Execute();
+
+      void Execute(const Dictionary& parameters);
+
+      IDatabase& GetDatabase()
+      {
+        return database_;
+      }
+      
+      bool IsDone() const;
+      
+      void Next();
+
+      size_t GetResultFieldsCount() const;
+
+      void SetResultFieldType(size_t field,
+                              ValueType type);
+      
+      const IValue& GetResultField(size_t index) const;
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/DatabasesEnumerations.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,42 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+
+namespace OrthancDatabases
+{
+  enum ValueType
+  {
+    ValueType_BinaryString,
+    ValueType_File,
+    ValueType_Integer64,
+    ValueType_Null,
+    ValueType_Utf8String
+  };
+
+  enum Dialect
+  {
+    Dialect_MySQL,
+    Dialect_PostgreSQL,
+    Dialect_SQLite
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Dictionary.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,131 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Dictionary.h"
+
+#include "BinaryStringValue.h"
+#include "Integer64Value.h"
+#include "NullValue.h"
+#include "Utf8StringValue.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <cassert>
+
+namespace OrthancDatabases
+{
+  Dictionary::~Dictionary()
+  {
+    for (Values::iterator it = values_.begin(); 
+         it != values_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+
+
+  bool Dictionary::HasKey(const std::string& key) const
+  {
+    return values_.find(key) != values_.end();
+  }
+
+
+  void Dictionary::Remove(const std::string& key)
+  {
+    Values::iterator found = values_.find(key);
+
+    if (found != values_.end())
+    {
+      assert(found->second != NULL);
+      delete found->second;
+      values_.erase(found);
+    }
+  }
+
+  
+  void Dictionary::SetValue(const std::string& key,
+                            IValue* value)   // Takes ownership
+  {
+    if (value == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    Values::iterator found = values_.find(key);
+
+    if (found == values_.end())
+    {
+      values_[key] = value;
+    }
+    else
+    {
+      assert(found->second != NULL);
+      delete found->second;
+      found->second = value;
+    }      
+  }
+
+  
+  void Dictionary::SetUtf8Value(const std::string& key,
+                                const std::string& utf8)
+  {
+    SetValue(key, new Utf8StringValue(utf8));
+  }
+
+  
+  void Dictionary::SetBinaryValue(const std::string& key,
+                                  const std::string& binary)
+  {
+    SetValue(key, new BinaryStringValue(binary));
+  }
+
+  
+  void Dictionary::SetIntegerValue(const std::string& key,
+                                   int64_t value)
+  {
+    SetValue(key, new Integer64Value(value));
+  }
+
+  
+  void Dictionary::SetNullValue(const std::string& key)
+  {
+    SetValue(key, new NullValue);
+  }
+
+  
+  const IValue& Dictionary::GetValue(const std::string& key) const
+  {
+    Values::const_iterator found = values_.find(key);
+
+    if (found == values_.end())
+    {
+      LOG(ERROR) << "Inexistent value in a dictionary: " << key;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      assert(found->second != NULL);
+      return *found->second;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Dictionary.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+#include <map>
+
+namespace OrthancDatabases
+{
+  class Dictionary : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, IValue*>   Values;
+
+    Values  values_;
+
+  public:
+    ~Dictionary();
+
+    bool HasKey(const std::string& key) const;
+
+    void Remove(const std::string& key);
+
+    void SetValue(const std::string& key,
+                  IValue* value);   // Takes ownership
+
+    void SetUtf8Value(const std::string& key,
+                      const std::string& utf8);
+
+    void SetBinaryValue(const std::string& key,
+                        const std::string& binary);
+
+    void SetIntegerValue(const std::string& key,
+                         int64_t value);
+
+    void SetNullValue(const std::string& key);
+
+    const IValue& GetValue(const std::string& key) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/FileValue.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "FileValue.h"
+
+#include "BinaryStringValue.h"
+#include "NullValue.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  IValue* FileValue::Convert(ValueType target) const
+  {
+    switch (target)
+    {
+      case ValueType_BinaryString:
+        return new BinaryStringValue(content_);
+
+      case ValueType_Null:
+        return new NullValue;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+  }
+
+  std::string FileValue::Format() const
+  {
+    return "(file - " + boost::lexical_cast<std::string>(content_.size()) + " bytes)";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/FileValue.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,88 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  class FileValue : public IValue
+  {
+  private:
+    std::string  content_;
+
+  public:
+    FileValue()
+    {
+    }
+
+    FileValue(const std::string& content) :
+      content_(content)
+    {
+    }
+
+    FileValue(const void* buffer,
+              size_t size)
+    {
+      content_.assign(reinterpret_cast<const char*>(buffer), size);
+    }
+    
+    void SwapContent(std::string& content)
+    {
+      content_.swap(content);
+    }
+
+    void SetContent(const std::string& content)
+    {
+      content_ = content;
+    }
+
+    std::string& GetContent()
+    {
+      return content_;
+    }
+
+    const std::string& GetContent() const
+    {
+      return content_;
+    }
+
+    const void* GetBuffer() const
+    {
+      return (content_.empty() ? NULL : content_.c_str());
+    }
+
+    size_t GetSize() const
+    {
+      return content_.size();
+    }
+
+    virtual ValueType GetType() const
+    {
+      return ValueType_File;
+    }
+    
+    virtual IValue* Convert(ValueType target) const;
+
+    virtual std::string Format() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/GenericFormatter.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,99 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GenericFormatter.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  void GenericFormatter::Format(std::string& target,
+                                const std::string& source,
+                                ValueType type)
+  {
+    if (source.empty())
+    {
+      // This is the default parameter for INSERT
+      switch (dialect_)
+      {
+        case Dialect_PostgreSQL:
+          target = "DEFAULT";
+          break;
+
+        case Dialect_MySQL:
+        case Dialect_SQLite:
+          target = "NULL";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    else
+    {
+      switch (dialect_)
+      {
+        case Dialect_PostgreSQL:
+          target = "$" + boost::lexical_cast<std::string>(parametersName_.size() + 1);
+          break;
+
+        case Dialect_MySQL:
+        case Dialect_SQLite:
+          target = "?";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+      parametersName_.push_back(source);
+      parametersType_.push_back(type);
+    }
+  }
+
+
+  const std::string& GenericFormatter::GetParameterName(size_t index) const
+  {
+    if (index >= parametersName_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return parametersName_[index];
+    }
+  }
+
+    
+  ValueType GenericFormatter::GetParameterType(size_t index) const
+  {
+    if (index >= parametersType_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return parametersType_[index];
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/GenericFormatter.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,54 @@
+ /**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Query.h"
+
+namespace OrthancDatabases
+{
+  class GenericFormatter : public Query::IParameterFormatter
+  {
+  private:
+    Dialect                   dialect_;
+    std::vector<std::string>  parametersName_;
+    std::vector<ValueType>    parametersType_;
+      
+  public:
+    GenericFormatter(Dialect dialect) :
+      dialect_(dialect)
+    {
+    }
+    
+    void Format(std::string& target,
+                const std::string& source,
+                ValueType type);
+
+    size_t GetParametersCount() const
+    {
+      return parametersName_.size();
+    }
+
+    const std::string& GetParameterName(size_t index) const;
+
+    ValueType GetParameterType(size_t index) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/IDatabase.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,43 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IPrecompiledStatement.h"
+#include "ITransaction.h"
+#include "Query.h"
+
+namespace OrthancDatabases
+{
+  class IDatabase : public boost::noncopyable
+  {
+  public:
+    virtual ~IDatabase()
+    {
+    }
+
+    virtual Dialect GetDialect() const = 0;
+
+    virtual IPrecompiledStatement* Compile(const Query& query) = 0;
+
+    virtual ITransaction* CreateTransaction() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/IDatabaseFactory.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,39 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDatabase.h"
+
+namespace OrthancDatabases
+{
+  class IDatabaseFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IDatabaseFactory()
+    {
+    }
+
+    virtual Dialect GetDialect() const = 0;
+
+    virtual IDatabase* Open() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/IPrecompiledStatement.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,37 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancDatabases
+{
+  class IPrecompiledStatement : public boost::noncopyable
+  {
+  public:
+    virtual ~IPrecompiledStatement()
+    {
+    }
+
+    virtual bool IsReadOnly() const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/IResult.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,46 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  class IResult : public boost::noncopyable
+  {
+  public:
+    virtual ~IResult()
+    {
+    }
+
+    virtual void SetExpectedType(size_t field,
+                                 ValueType type) = 0;
+
+    virtual bool IsDone() const = 0;
+
+    virtual void Next() = 0;
+
+    virtual size_t GetFieldsCount() const = 0;
+
+    virtual const IValue& GetField(size_t index) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/ITransaction.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Dictionary.h"
+#include "IPrecompiledStatement.h"
+#include "IResult.h"
+
+namespace OrthancDatabases
+{
+  class ITransaction : public boost::noncopyable
+  {
+  public:
+    virtual ~ITransaction()
+    {
+    }
+
+    virtual bool IsReadOnly() const = 0;
+
+    virtual void Rollback() = 0;
+    
+    virtual void Commit() = 0;
+
+    virtual IResult* Execute(IPrecompiledStatement& statement,
+                             const Dictionary& parameters) = 0;
+
+    virtual void ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                      const Dictionary& parameters) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/IValue.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,44 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DatabasesEnumerations.h"
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+namespace OrthancDatabases
+{
+  class IValue : public boost::noncopyable
+  {
+  public:
+    virtual ~IValue()
+    {
+    }
+
+    virtual ValueType GetType() const = 0;
+
+    virtual IValue* Convert(ValueType target) const = 0;
+
+    virtual std::string Format() const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Integer64Value.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,62 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Integer64Value.h"
+
+#include "BinaryStringValue.h"
+#include "FileValue.h"
+#include "NullValue.h"
+#include "Utf8StringValue.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  IValue* Integer64Value::Convert(ValueType target) const
+  {
+    std::string s = boost::lexical_cast<std::string>(value_);
+            
+    switch (target)
+    {
+      case ValueType_Null:
+        return new NullValue;
+
+      case ValueType_BinaryString:
+        return new BinaryStringValue(s);
+
+      case ValueType_File:
+        return new FileValue(s);
+
+      case ValueType_Utf8String:
+        return new Utf8StringValue(s);
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  std::string Integer64Value::Format() const
+  {
+    return boost::lexical_cast<std::string>(value_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Integer64Value.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  class Integer64Value : public IValue
+  {
+  private:
+    int64_t  value_;
+
+  public:
+    explicit Integer64Value(int64_t value) :
+    value_(value)
+    {
+    }
+
+    int64_t GetValue() const
+    {
+      return value_;
+    }
+
+    virtual ValueType GetType() const
+    {
+      return ValueType_Integer64;
+    }
+    
+    virtual IValue* Convert(ValueType target) const;
+
+    virtual std::string Format() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/NullValue.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,39 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "NullValue.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  IValue* NullValue::Convert(ValueType target) const
+  {
+    if (target == ValueType_Null)
+    {
+      return new NullValue;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/NullValue.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,43 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  class NullValue : public IValue
+  {
+  public:
+    virtual ValueType GetType() const
+    {
+      return ValueType_Null;
+    }
+
+    virtual IValue* Convert(ValueType target) const;
+
+    virtual std::string Format() const
+    {
+      return "(null)";
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Query.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,178 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Query.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/regex.hpp>
+
+namespace OrthancDatabases
+{
+  class Query::Token : public boost::noncopyable
+  {
+  private:
+    bool         isParameter_;
+    std::string  content_;
+
+  public:
+    Token(bool isParameter,
+          const std::string& content) :
+      isParameter_(isParameter),
+      content_(content)
+    {
+    }
+
+    bool IsParameter() const
+    {
+      return isParameter_;
+    }
+
+    const std::string& GetContent() const
+    {
+      return content_;
+    }
+  };
+
+
+  void Query::Setup(const std::string& sql)
+  {
+    boost::regex regex("\\$\\{(.*?)\\}");
+
+    std::string::const_iterator last = sql.begin();
+    boost::sregex_token_iterator it(sql.begin(), sql.end(), regex, 0);
+    boost::sregex_token_iterator end;
+
+    while (it != end)
+    {
+      if (last != it->first)
+      {
+        tokens_.push_back(new Token(false, std::string(last, it->first)));
+      }
+
+      std::string parameter = *it;
+      assert(parameter.size() >= 3);
+      parameter = parameter.substr(2, parameter.size() - 3);
+
+      tokens_.push_back(new Token(true, parameter));
+      parameters_[parameter] = ValueType_Null;
+
+      last = it->second;
+
+      ++it;
+    }
+
+    if (last != sql.end())
+    {
+      tokens_.push_back(new Token(false, std::string(last, sql.end())));
+    }
+  }
+
+
+  Query::Query(const std::string& sql) :
+    readOnly_(false)
+  {
+    Setup(sql);
+  }
+
+
+  Query::Query(const std::string& sql,
+               bool readOnly) :
+    readOnly_(readOnly)
+  {
+    Setup(sql);
+  }
+
+  
+  Query::~Query()
+  {
+    for (size_t i = 0; i < tokens_.size(); i++)
+    {
+      assert(tokens_[i] != NULL);
+      delete tokens_[i];
+    }
+  }
+
+
+  bool Query::HasParameter(const std::string& parameter) const
+  {
+    return parameters_.find(parameter) != parameters_.end();
+  }
+
+  
+  ValueType Query::GetType(const std::string& parameter) const
+  {
+    Parameters::const_iterator found = parameters_.find(parameter);
+
+    if (found == parameters_.end())
+    {
+      LOG(ERROR) << "Inexistent parameter in a SQL query: " << parameter;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+    }
+    else
+    {
+      return found->second;
+    }
+  }
+
+
+  void Query::SetType(const std::string& parameter,
+                      ValueType type)
+  {
+    Parameters::iterator found = parameters_.find(parameter);
+
+    if (found == parameters_.end())
+    {
+      LOG(ERROR) << "Ignoring inexistent parameter in a SQL query: " << parameter;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      found->second = type;
+    }
+  }
+
+  
+  void Query::Format(std::string& result,
+                     IParameterFormatter& formatter) const
+  {
+    result.clear();
+
+    for (size_t i = 0; i < tokens_.size(); i++)
+    {
+      assert(tokens_[i] != NULL);
+
+      const std::string& content = tokens_[i]->GetContent();
+
+      if (tokens_[i]->IsParameter())
+      {
+        std::string parameter;
+        formatter.Format(parameter, content, GetType(content));
+        result += parameter;
+      }
+      else
+      {
+        result += content;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Query.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,88 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DatabasesEnumerations.h"
+
+#include <map>
+#include <vector>
+#include <string>
+#include <boost/noncopyable.hpp>
+
+
+namespace OrthancDatabases
+{
+  class Query : public boost::noncopyable
+  {
+  public:
+    class IParameterFormatter : public boost::noncopyable
+    {
+    public:
+      virtual ~IParameterFormatter()
+      {
+      }
+
+      virtual void Format(std::string& target,
+                          const std::string& source,
+                          ValueType type) = 0;
+    };
+
+  private:
+    typedef std::map<std::string, ValueType>  Parameters;
+
+    class Token;
+
+    std::vector<Token*>  tokens_;
+    Parameters           parameters_;
+    bool                 readOnly_;
+
+    void Setup(const std::string& sql);
+
+  public:
+    Query(const std::string& sql);
+
+    Query(const std::string& sql,
+          bool isReadOnly);
+
+    ~Query();
+
+    bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    void SetReadOnly(bool isReadOnly)
+    {
+      readOnly_ = isReadOnly;
+    }
+
+    bool HasParameter(const std::string& parameter) const;
+
+    ValueType GetType(const std::string& parameter) const;
+
+    void SetType(const std::string& parameter,
+                 ValueType type);
+
+    void Format(std::string& result,
+                IParameterFormatter& formatter) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/ResultBase.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,160 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ResultBase.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/Utf8StringValue.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <cassert>
+#include <memory>
+
+namespace OrthancDatabases
+{
+  void ResultBase::ClearFields()
+  {
+    for (size_t i = 0; i < fields_.size(); i++)
+    {
+      if (fields_[i] != NULL)
+      {
+        delete fields_[i];
+        fields_[i] = NULL;
+      }
+    }
+  }
+
+
+  void ResultBase::ConvertFields()
+  {
+    assert(expectedType_.size() == fields_.size() &&
+           hasExpectedType_.size() == fields_.size());
+      
+    for (size_t i = 0; i < fields_.size(); i++)
+    {
+      if (fields_[i] == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      ValueType sourceType = fields_[i]->GetType();
+      ValueType targetType = expectedType_[i];
+
+      if (hasExpectedType_[i] &&
+          sourceType != ValueType_Null &&
+          sourceType != targetType)
+      {
+        std::auto_ptr<IValue> converted(fields_[i]->Convert(targetType));
+        
+        if (converted.get() == NULL)
+        {
+          LOG(ERROR) << "Cannot convert between data types from a database";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+        }
+        else
+        {
+          assert(fields_[i] != NULL);
+          delete fields_[i];
+          fields_[i] = converted.release();
+        }
+      }
+    }
+  }
+
+
+  void ResultBase::FetchFields()
+  {
+    ClearFields();
+
+    if (!IsDone())
+    {
+      for (size_t i = 0; i < fields_.size(); i++)
+      {
+        fields_[i] = FetchField(i);
+
+        if (fields_[i] == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      ConvertFields();
+    }
+  }
+    
+
+  void ResultBase::SetFieldsCount(size_t count)
+  {
+    if (!fields_.empty())
+    {
+      // This method can only be invoked once
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    
+    fields_.resize(count);
+    expectedType_.resize(count, ValueType_Null);
+    hasExpectedType_.resize(count, false);
+  }
+    
+
+  void ResultBase::SetExpectedType(size_t field,
+                                   ValueType type)
+  {
+    assert(expectedType_.size() == fields_.size() &&
+           hasExpectedType_.size() == fields_.size());
+      
+    if (field < fields_.size())
+    {
+      expectedType_[field] = type;
+      hasExpectedType_[field] = true;
+
+      if (!IsDone())
+      {
+        ConvertFields();
+      }
+    }
+  }
+  
+
+  const IValue& ResultBase::GetField(size_t index) const
+  {
+    if (IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (index >= fields_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (fields_[index] == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return *fields_[index];
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/ResultBase.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IResult.h"
+
+#include <vector>
+
+namespace OrthancDatabases
+{
+  class ResultBase : public IResult
+  {
+  private:
+    void ClearFields();
+
+    void ConvertFields();
+
+    std::vector<IValue*>   fields_;
+    std::vector<ValueType> expectedType_;
+    std::vector<bool>      hasExpectedType_;
+    
+  protected:
+    virtual IValue* FetchField(size_t index) = 0;
+
+    void FetchFields();
+
+    void SetFieldsCount(size_t count);
+    
+  public:
+    virtual ~ResultBase()
+    {
+      ClearFields();
+    }
+
+    virtual void SetExpectedType(size_t field,
+                                 ValueType type);
+
+    virtual size_t GetFieldsCount() const
+    {
+      return fields_.size();
+    }
+
+    virtual const IValue& GetField(size_t index) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/StatementLocation.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,39 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "StatementLocation.h"
+
+#include <string.h>
+
+namespace OrthancDatabases
+{
+  bool StatementLocation::operator< (const StatementLocation& other) const
+  {
+    if (line_ != other.line_)
+    {
+      return line_ < other.line_;
+    }
+    else
+    {
+      return strcmp(file_, other.file_) < 0;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/StatementLocation.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#define STATEMENT_FROM_HERE  ::OrthancDatabases::StatementLocation(__FILE__, __LINE__)
+
+
+namespace OrthancDatabases
+{
+  class StatementLocation
+  {
+  private:
+    const char* file_;
+    int line_;
+    
+    StatementLocation(); // Forbidden
+    
+  public:
+    StatementLocation(const char* file,
+                      int line) :
+      file_(file),
+      line_(line)
+    {
+    }
+
+    const char* GetFile() const
+    {
+      return file_;
+    }
+
+    int GetLine() const
+    {
+      return line_;
+    }
+    
+    bool operator< (const StatementLocation& other) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Utf8StringValue.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,65 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Utf8StringValue.h"
+
+#include "BinaryStringValue.h"
+#include "FileValue.h"
+#include "NullValue.h"
+#include "Integer64Value.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  IValue* Utf8StringValue::Convert(ValueType target) const
+  {
+    switch (target)
+    {
+      case ValueType_Null:
+        return new NullValue;
+
+      case ValueType_BinaryString:
+        return new BinaryStringValue(utf8_);
+
+      case ValueType_File:
+        return new FileValue(utf8_);
+
+      case ValueType_Integer64:
+        try
+        {
+          int64_t value = boost::lexical_cast<int64_t>(utf8_);
+          return new Integer64Value(value);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Common/Utf8StringValue.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IValue.h"
+
+namespace OrthancDatabases
+{
+  // Represents an UTF-8 string
+  class Utf8StringValue : public IValue
+  {
+  private:
+    std::string  utf8_;
+
+  public:
+    explicit Utf8StringValue()
+    {
+    }
+
+    explicit Utf8StringValue(const std::string& utf8) :
+      utf8_(utf8)
+    {
+    }
+
+    explicit Utf8StringValue(const char* utf8) :
+      utf8_(utf8)
+    {
+    }
+
+    const std::string& GetContent() const
+    {
+      return utf8_;
+    }
+
+    virtual ValueType GetType() const
+    {
+      return ValueType_Utf8String;
+    }
+    
+    virtual IValue* Convert(ValueType target) const;
+
+    virtual std::string Format() const
+    {
+      return "[" + utf8_ + "]";
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLDatabase.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,270 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLDatabase.h"
+
+#include "MySQLResult.h"
+#include "MySQLStatement.h"
+#include "MySQLTransaction.h"
+#include "../Common/Integer64Value.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <mysql/errmsg.h>
+#include <mysqld_error.h>
+
+#include <memory>
+
+namespace OrthancDatabases
+{
+  void MySQLDatabase::Close()
+  {
+    if (mysql_ != NULL)
+    {
+      LOG(INFO) << "Closing connection to MySQL database";
+      mysql_close(mysql_);
+      mysql_ = NULL;
+    }
+  }
+
+
+  void MySQLDatabase::CheckErrorCode(int code)
+  {
+    if (code == 0)
+    {
+      return;
+    }
+    else
+    {
+      LogError();
+      
+      unsigned int error = mysql_errno(mysql_);
+      if (error == CR_SERVER_GONE_ERROR ||
+          error == CR_SERVER_LOST ||
+          error == ER_QUERY_INTERRUPTED)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }
+    }
+  }
+
+
+  MySQLDatabase::MySQLDatabase(const MySQLParameters& parameters) :
+    parameters_(parameters),
+    mysql_(NULL)
+  {
+  }
+
+
+  void MySQLDatabase::LogError()
+  {
+    if (mysql_ != NULL)
+    {
+      LOG(ERROR) << "MySQL error (" << mysql_errno(mysql_)
+                 << "," << mysql_sqlstate(mysql_)
+                 << "): " << mysql_error(mysql_);
+    }
+  }
+
+  
+  MYSQL* MySQLDatabase::GetObject()
+  {
+    if (mysql_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return mysql_;
+    }
+  }
+
+  
+  void MySQLDatabase::Open()
+  {
+    if (mysql_ != NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    mysql_ = mysql_init(NULL);
+    if (mysql_ == NULL)
+    {
+      LOG(ERROR) << "Cannot initialize the MySQL connector";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+      
+    const char* db = (parameters_.GetDatabase().empty() ? NULL :
+                      parameters_.GetDatabase().c_str());
+
+    const char* socket = (parameters_.GetUnixSocket().empty() ? NULL :
+                          parameters_.GetUnixSocket().c_str());
+
+    if (mysql_real_connect(mysql_,
+                           parameters_.GetHost().c_str(),
+                           parameters_.GetUsername().c_str(),
+                           parameters_.GetPassword().c_str(), db,
+                           parameters_.GetPort(), socket, 0) == 0)
+    {
+      LogError();
+      Close();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
+    }
+    else
+    {
+      LOG(INFO) << "Successful connection to MySQL database";
+    }
+
+    if (mysql_set_character_set(mysql_, "utf8mb4") != 0)
+    {
+      LOG(ERROR) << "Cannot set the character set to UTF8";
+      Close();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+    }
+
+    if (parameters_.HasLock())
+    {
+      try
+      {
+        Query query("SELECT GET_LOCK('Lock', 0);", false);
+        MySQLStatement statement(*this, query);
+
+        MySQLTransaction t(*this);
+        Dictionary args;
+
+        std::auto_ptr<IResult> result(t.Execute(statement, args));
+
+        if (result->IsDone() ||
+            result->GetField(0).GetType() != ValueType_Integer64 ||
+            dynamic_cast<const Integer64Value&>(result->GetField(0)).GetValue() != 1)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+        }
+
+        t.Commit();
+      }
+      catch (Orthanc::OrthancException&)
+      {
+        LOG(ERROR) << "The MySQL database is locked by another instance of Orthanc";
+        Close();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }
+    }
+  }
+
+  
+  bool MySQLDatabase::DoesTableExist(MySQLTransaction& transaction,
+                                     const std::string& name)
+  {
+    if (mysql_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    for (size_t i = 0; i < name.length(); i++)
+    {
+      if (!isalnum(name[i]))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+    Query query("SELECT COUNT(*) FROM information_schema.TABLES WHERE "
+                "(TABLE_SCHEMA = ${database}) AND (TABLE_NAME = ${table})", true);
+    query.SetType("database", ValueType_Utf8String);
+    query.SetType("table", ValueType_Utf8String);
+    
+    MySQLStatement statement(*this, query);
+
+    Dictionary args;
+    args.SetUtf8Value("database", parameters_.GetDatabase());
+    args.SetUtf8Value("table", name);
+
+    std::auto_ptr<IResult> result(statement.Execute(transaction, args));
+    return (!result->IsDone() &&
+            result->GetFieldsCount() == 1 &&
+            result->GetField(0).GetType() == ValueType_Integer64 &&
+            dynamic_cast<const Integer64Value&>(result->GetField(0)).GetValue() == 1);            
+  }
+
+
+  void MySQLDatabase::Execute(const std::string& sql)
+  {
+    if (mysql_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    // This emulates the behavior of "CLIENT_MULTI_STATEMENTS" in
+    // "mysql_real_connect()", avoiding to implement a loop over
+    // "mysql_query()"
+    std::vector<std::string> commands;
+    Orthanc::Toolbox::TokenizeString(commands, sql, ';');
+
+    for (size_t i = 0; i < commands.size(); i++)
+    {
+      std::string s = Orthanc::Toolbox::StripSpaces(commands[i]);
+
+      if (!s.empty())
+      {
+        // Replace the escape character "@" by a semicolon
+        std::replace(s.begin(), s.end(), '@', ';');
+      
+        LOG(TRACE) << "MySQL: " << s;
+        CheckErrorCode(mysql_query(mysql_, s.c_str()));
+      }
+    }
+  }
+
+  
+  IPrecompiledStatement* MySQLDatabase::Compile(const Query& query)
+  {
+    if (mysql_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return new MySQLStatement(*this, query);
+  }
+
+
+  ITransaction* MySQLDatabase::CreateTransaction()
+  {
+    if (mysql_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    return new MySQLTransaction(*this);
+  }
+
+  
+  void MySQLDatabase::GlobalFinalization()
+  {
+    mysql_library_end();
+  } 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLDatabase.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_MYSQL != 1
+#  error MySQL support must be enabled to use this file
+#endif
+
+#include "../Common/IDatabase.h"
+#include "MySQLParameters.h"
+
+#include <mysql.h>
+
+namespace OrthancDatabases
+{
+  class MySQLTransaction;
+  
+  class MySQLDatabase : public IDatabase
+  {
+  private:
+    MySQLParameters  parameters_;
+    MYSQL           *mysql_;
+
+    void Close();
+
+  public:
+    MySQLDatabase(const MySQLParameters& parameters);
+
+    virtual ~MySQLDatabase()
+    {
+      Close();
+    }
+
+    void LogError();
+
+    void CheckErrorCode(int code);
+
+    MYSQL* GetObject();
+
+    void Open();
+
+    void Execute(const std::string& sql);
+
+    bool DoesTableExist(MySQLTransaction& transaction,
+                        const std::string& name);
+
+    virtual Dialect GetDialect() const
+    {
+      return Dialect_MySQL;
+    }
+
+    virtual IPrecompiledStatement* Compile(const Query& query);
+
+    virtual ITransaction* CreateTransaction();
+
+    static void GlobalFinalization();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLParameters.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,128 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLParameters.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  void MySQLParameters::Reset()
+  {
+    host_ = "localhost";
+    username_.clear();
+    password_.clear();
+    database_.clear();
+    port_ = 3306;
+    unixSocket_ = "/var/run/mysqld/mysqld.sock";
+    lock_ = true;
+  }
+
+  
+  MySQLParameters::MySQLParameters()
+  {
+    Reset();
+  }
+
+
+  MySQLParameters::MySQLParameters(const OrthancPlugins::OrthancConfiguration& configuration)
+  {
+    Reset();
+
+    std::string s;
+    if (configuration.LookupStringValue(s, "Host"))
+    {
+      SetHost(s);
+    }
+
+    if (configuration.LookupStringValue(s, "Username"))
+    {
+      SetUsername(s);
+    }
+
+    if (configuration.LookupStringValue(s, "Password"))
+    {
+      SetPassword(s);
+    }
+
+    if (configuration.LookupStringValue(s, "Database"))
+    {
+      SetDatabase(s);
+    }
+
+    unsigned int port;
+    if (configuration.LookupUnsignedIntegerValue(port, "Port"))
+    {
+      SetPort(port);
+    }
+
+    if (configuration.LookupStringValue(s, "UnixSocket"))
+    {
+      SetUnixSocket(s);
+    }
+
+    lock_ = configuration.GetBooleanValue("Lock", true);  // Use locking by default
+  }
+
+
+  void MySQLParameters::SetHost(const std::string& host)
+  {
+    host_ = host;
+  }
+
+  
+  void MySQLParameters::SetUsername(const std::string& username)
+  {
+    username_ = username;
+  }
+
+  
+  void MySQLParameters::SetPassword(const std::string& password)
+  {
+    password_ = password;
+  }
+
+  
+  void MySQLParameters::SetDatabase(const std::string& database)
+  {
+    database_ = database;
+  }
+
+  
+  void MySQLParameters::SetPort(unsigned int port)
+  {
+    if (port >= 65535)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      port_ = port;
+    }
+  }
+
+  
+  void MySQLParameters::SetUnixSocket(const std::string& socket)
+  {
+    unixSocket_ = socket;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLParameters.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,102 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_MYSQL != 1
+#  error MySQL support must be enabled to use this file
+#endif
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+namespace OrthancDatabases
+{
+  class MySQLParameters
+  {
+  private:
+    std::string  host_;
+    std::string  username_;
+    std::string  password_;
+    std::string  database_;
+    uint16_t     port_;
+    std::string  unixSocket_;
+    bool         lock_;
+
+    void Reset();
+
+  public:
+    MySQLParameters();
+
+    MySQLParameters(const OrthancPlugins::OrthancConfiguration& configuration);
+
+    const std::string& GetHost() const
+    {
+      return host_;
+    }
+
+    const std::string& GetUsername() const
+    {
+      return username_;
+    }
+
+    const std::string& GetPassword() const
+    {
+      return password_;
+    }
+
+    const std::string& GetDatabase() const
+    {
+      return database_;
+    }
+
+    const std::string& GetUnixSocket() const
+    {
+      return unixSocket_;
+    }
+
+    uint16_t GetPort() const
+    {
+      return port_;
+    }
+
+    void SetHost(const std::string& host);
+    
+    void SetUsername(const std::string& username);
+
+    void SetPassword(const std::string& password);
+
+    void SetDatabase(const std::string& database);
+
+    void SetPort(unsigned int port);
+
+    void SetUnixSocket(const std::string& socket);
+
+    void SetLock(bool lock)
+    {
+      lock_ = lock;
+    }
+
+    bool HasLock() const
+    {
+      return lock_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLResult.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLResult.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <mysql/errmsg.h>
+#include <mysqld_error.h>
+
+namespace OrthancDatabases
+{
+  void MySQLResult::Step()
+  {
+    int code = mysql_stmt_fetch(statement_.GetObject());
+
+    if (code == 1)
+    {
+      unsigned int error = mysql_errno(database_.GetObject());
+      if (error == 0)
+      {
+        // This case can occur if the SQL request is not a SELECT
+        done_ = true;
+      }
+      else if (error == CR_SERVER_GONE_ERROR ||
+               error == CR_SERVER_LOST ||
+               error == ER_QUERY_INTERRUPTED)
+      {
+        database_.LogError();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
+      }
+      else
+      {
+        database_.LogError();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }
+    }
+    else
+    {
+      done_ = (code != 0 &&
+               code != MYSQL_DATA_TRUNCATED);  // Occurs if mysql_stmt_fetch_column() must be called
+
+      FetchFields();
+    }
+  }
+
+
+  IValue* MySQLResult::FetchField(size_t index)
+  {
+    return statement_.FetchResultField(index);
+  }
+  
+  
+  MySQLResult::MySQLResult(MySQLDatabase& db,
+                           MySQLStatement& statement) :
+    database_(db),
+    statement_(statement)
+  {
+    // !!! https://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-fetch.html
+    // https://gist.github.com/hoterran/6365915
+    // https://github.com/hholzgra/connector-c-examples/blob/master/mysql_stmt_bind_result.c
+
+    SetFieldsCount(statement_.GetResultFieldsCount());
+
+    Step();
+  }
+    
+
+  MySQLResult::~MySQLResult()
+  {
+    // Reset the statement for further use
+    if (mysql_stmt_reset(statement_.GetObject()) != 0)
+    {
+      LOG(ERROR) << "Cannot reset the statement, expect an error";
+    }
+  }
+    
+
+  void MySQLResult::Next()
+  {
+    if (IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      Step();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLResult.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_MYSQL != 1
+#  error MySQL support must be enabled to use this file
+#endif
+
+#include "../Common/ResultBase.h"
+#include "MySQLStatement.h"
+
+namespace OrthancDatabases
+{
+  class MySQLResult : public ResultBase
+  {
+  private:
+    MySQLDatabase&   database_;
+    MySQLStatement&  statement_;
+    bool             done_;
+
+    void Step();
+
+  protected:
+    virtual IValue* FetchField(size_t index);
+    
+  public:
+    MySQLResult(MySQLDatabase& db,
+                MySQLStatement& statement);
+
+    virtual ~MySQLResult();
+    
+    virtual bool IsDone() const
+    {
+      return done_;
+    }
+
+    virtual void Next();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLStatement.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,532 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLStatement.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/FileValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/Utf8StringValue.h"
+#include "MySQLResult.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <list>
+#include <memory>
+
+namespace OrthancDatabases
+{
+  class MySQLStatement::ResultField : public boost::noncopyable
+  {
+  private:     
+    IValue* CreateIntegerValue(MYSQL_BIND& bind) const
+    {
+      if (length_ != buffer_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+            
+      switch (mysqlType_)
+      {
+        case MYSQL_TYPE_TINY:
+          if (bind.is_unsigned)
+          {
+            return new Integer64Value(*reinterpret_cast<const uint8_t*>(&buffer_[0]));
+          }
+          else
+          {
+            return new Integer64Value(*reinterpret_cast<const int8_t*>(&buffer_[0]));
+          }
+                
+        case MYSQL_TYPE_SHORT:
+          if (bind.is_unsigned)
+          {
+            return new Integer64Value(*reinterpret_cast<const uint16_t*>(&buffer_[0]));
+          }
+          else
+          {
+            return new Integer64Value(*reinterpret_cast<const int16_t*>(&buffer_[0]));
+          }
+
+          break;
+
+        case MYSQL_TYPE_LONG:
+          if (bind.is_unsigned)
+          {
+            return new Integer64Value(*reinterpret_cast<const uint32_t*>(&buffer_[0]));
+          }
+          else
+          {
+            return new Integer64Value(*reinterpret_cast<const int32_t*>(&buffer_[0]));
+          }
+
+          break;
+            
+        case MYSQL_TYPE_LONGLONG:
+          if (bind.is_unsigned)
+          {
+            uint64_t value = *reinterpret_cast<const uint64_t*>(&buffer_[0]);
+            if (static_cast<uint64_t>(static_cast<int64_t>(value)) != value)
+            {
+              LOG(WARNING) << "Overflow in a 64 bit integer";
+            }
+
+            return new Integer64Value(static_cast<int64_t>(value));
+          }
+          else
+          {
+            return new Integer64Value(*reinterpret_cast<const int64_t*>(&buffer_[0]));
+          }
+
+          break;
+              
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);              
+      }
+    }
+        
+
+    enum enum_field_types   mysqlType_;
+    ValueType               orthancType_;
+    std::string             buffer_;
+    my_bool                 isNull_;
+    my_bool                 isError_;
+    unsigned long           length_;
+
+
+  public:
+    ResultField(const MYSQL_FIELD& field) :
+      mysqlType_(field.type)
+    {
+      // https://dev.mysql.com/doc/refman/8.0/en/c-api-data-structures.html
+      // https://dev.mysql.com/doc/refman/8.0/en/mysql-stmt-fetch.html => size of "buffer_"
+      switch (field.type)
+      {
+        case MYSQL_TYPE_TINY:
+          orthancType_ = ValueType_Integer64;
+          buffer_.resize(1);
+          break;
+              
+        case MYSQL_TYPE_SHORT:
+          orthancType_ = ValueType_Integer64;
+          buffer_.resize(2);
+          break;
+
+        case MYSQL_TYPE_LONG:
+          orthancType_ = ValueType_Integer64;
+          buffer_.resize(4);
+          break;
+            
+        case MYSQL_TYPE_LONGLONG:
+          orthancType_ = ValueType_Integer64;
+          buffer_.resize(8);
+          break;
+
+        case MYSQL_TYPE_STRING:
+        case MYSQL_TYPE_VAR_STRING:
+        case MYSQL_TYPE_BLOB:
+          // https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434
+          switch (field.charsetnr)
+          {
+            case 45:   // utf8mb4_general_ci
+            case 46:   // utf8mb4_bin
+            case 224:  // utf8mb4_unicode_ci  => RECOMMENDED collation
+              // https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
+              orthancType_ = ValueType_Utf8String;
+              break;
+
+            case 63:
+              orthancType_ = ValueType_BinaryString;
+              break;
+
+            default:
+              LOG(ERROR) << "Unsupported MySQL charset: " << field.charsetnr;
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);                
+          }
+
+          if (field.max_length > 0)
+          {
+            buffer_.resize(field.max_length);
+          }
+
+          break;
+          
+        default:
+          LOG(ERROR) << "MYSQL_TYPE not implemented: " << field.type;
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+        
+    enum enum_field_types GetMysqlType() const
+    {
+      return mysqlType_;
+    }
+
+    ValueType  GetOrthancType() const
+    {
+      return orthancType_;
+    }
+
+    void PrepareBind(MYSQL_BIND& bind)
+    {
+      memset(&bind, 0, sizeof(bind));
+
+      length_ = 0;
+
+      bind.buffer_length = buffer_.size();
+      bind.buffer_type = mysqlType_;
+      bind.is_null = &isNull_;
+      bind.length = &length_;
+
+      if (buffer_.empty())
+      {
+        // Only fetches the actual size of the field (*):
+        // mysql_stmt_fetch_column() must be invoked afterward
+        bind.buffer = 0;
+        isError_ = false;
+      }
+      else
+      {
+        bind.buffer = &buffer_[0];
+        bind.error = &isError_;
+      }
+    }
+
+
+    IValue* FetchValue(MySQLDatabase& database,
+                       MYSQL_STMT& statement,
+                       MYSQL_BIND& bind,
+                       unsigned int column) const
+    {
+      if (isError_)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }
+      else if (isNull_)
+      {
+        return new NullValue;
+      }
+      else if (orthancType_ == ValueType_Integer64)
+      {
+        return CreateIntegerValue(bind);
+      }
+      else if (orthancType_ == ValueType_Utf8String ||
+               orthancType_ == ValueType_BinaryString)
+      {
+        std::string tmp;
+        tmp.resize(length_);
+
+        if (!tmp.empty())
+        {
+          if (buffer_.empty())
+          {
+            bind.buffer = &tmp[0];
+            bind.buffer_length = tmp.size();
+
+            database.CheckErrorCode(mysql_stmt_fetch_column(&statement, &bind, column, 0));
+          }
+          else if (tmp.size() <= buffer_.size())
+          {
+            memcpy(&tmp[0], &buffer_[0], length_);
+          }
+          else
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+        }
+
+        if (orthancType_ == ValueType_Utf8String)
+        {
+          return new Utf8StringValue(tmp);
+        }
+        else
+        {
+          return new BinaryStringValue(tmp);
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }          
+    }
+  };
+
+
+  class MySQLStatement::ResultMetadata : public boost::noncopyable
+  {
+  private:
+    MYSQL_RES*              metadata_;
+      
+  public:
+    ResultMetadata(MySQLDatabase& db,
+                   MySQLStatement& statement) :
+      metadata_(NULL)
+    {
+      metadata_ = mysql_stmt_result_metadata(statement.GetObject());
+    }
+
+    ~ResultMetadata()
+    {
+      if (metadata_ != NULL)
+      {
+        mysql_free_result(metadata_);
+      }
+    }
+
+    bool HasFields() const
+    {
+      return metadata_ != NULL;
+    }
+
+    size_t GetFieldsCount()
+    {
+      if (HasFields())
+      {
+        return mysql_num_fields(metadata_);
+      }
+      else
+      {
+        return 0;
+      }
+    }
+
+    MYSQL_RES* GetObject()
+    {
+      return metadata_;
+    }
+  };
+    
+
+  void MySQLStatement::Close()
+  {
+    for (size_t i = 0; i < result_.size(); i++)
+    {
+      if (result_[i] != NULL)
+      {
+        delete result_[i];
+      }
+    }
+      
+    if (statement_ != NULL)
+    {
+      mysql_stmt_close(statement_);
+      statement_ = NULL;
+    }
+  }
+
+
+  MySQLStatement::MySQLStatement(MySQLDatabase& db,
+                                 const Query& query) :
+    db_(db),
+    readOnly_(query.IsReadOnly()),
+    statement_(NULL),
+    formatter_(Dialect_MySQL)
+  {
+    std::string sql;
+    query.Format(sql, formatter_);
+
+    statement_ = mysql_stmt_init(db.GetObject());
+    if (statement_ == NULL)
+    {
+      db.LogError();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+
+    LOG(INFO) << "Preparing MySQL statement: " << sql;
+
+    db_.CheckErrorCode(mysql_stmt_prepare(statement_, sql.c_str(), sql.size()));
+
+    if (mysql_stmt_param_count(statement_) != formatter_.GetParametersCount())
+    {
+      Close();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    try
+    {
+      ResultMetadata result(db, *this);
+
+      if (result.HasFields())
+      {
+        MYSQL_FIELD *field;
+        while ((field = mysql_fetch_field(result.GetObject())))
+        {
+          result_.push_back(new ResultField(*field));
+        }
+      }
+
+      if (result_.size() != result.GetFieldsCount())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      Close();
+      throw;
+    }
+
+    if (query.IsReadOnly())
+    {
+      unsigned long type = (unsigned long) CURSOR_TYPE_READ_ONLY;
+      mysql_stmt_attr_set(statement_, STMT_ATTR_CURSOR_TYPE, (void*) &type);
+    }
+  }
+
+
+  MYSQL_STMT* MySQLStatement::GetObject()
+  {
+    if (statement_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return statement_;
+    }
+  }
+
+
+  IValue* MySQLStatement::FetchResultField(size_t i)
+  {
+    if (i >= result_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(result_[i] != NULL);
+      return result_[i]->FetchValue(db_, *statement_, outputs_[i], i);
+    }
+  }
+
+
+  IResult* MySQLStatement::Execute(MySQLTransaction& transaction,
+                                   const Dictionary& parameters)
+  {
+    std::list<int>            intParameters;
+    std::list<long long int>  int64Parameters;
+
+    std::vector<MYSQL_BIND>  inputs(formatter_.GetParametersCount());
+
+    for (size_t i = 0; i < inputs.size(); i++)
+    {
+      memset(&inputs[i], 0, sizeof(MYSQL_BIND));
+
+      const std::string& name = formatter_.GetParameterName(i);
+      if (!parameters.HasKey(name))
+      {
+        LOG(ERROR) << "Missing required parameter in a SQL query: " << name;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
+      }
+
+      ValueType type = formatter_.GetParameterType(i);
+
+      const IValue& value = parameters.GetValue(name);
+      if (value.GetType() != type)
+      {
+        LOG(ERROR) << "Bad type of argument provided to a SQL query: " << name;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+      }
+
+      // https://dev.mysql.com/doc/refman/8.0/en/c-api-prepared-statement-type-codes.html
+      switch (type)
+      {
+        case ValueType_Integer64:
+        {
+          int64Parameters.push_back(dynamic_cast<const Integer64Value&>(value).GetValue());
+          inputs[i].buffer = &int64Parameters.back();
+          inputs[i].buffer_type = MYSQL_TYPE_LONGLONG;
+          break;
+        }
+
+        case ValueType_Utf8String:
+        {
+          const std::string& utf8 = dynamic_cast<const Utf8StringValue&>(value).GetContent();
+          inputs[i].buffer = const_cast<char*>(utf8.c_str());
+          inputs[i].buffer_length = utf8.size();
+          inputs[i].buffer_type = MYSQL_TYPE_STRING;
+          break;
+        }
+
+        case ValueType_BinaryString:
+        {
+          const std::string& content = dynamic_cast<const BinaryStringValue&>(value).GetContent();
+          inputs[i].buffer = const_cast<char*>(content.c_str());
+          inputs[i].buffer_length = content.size();
+          inputs[i].buffer_type = MYSQL_TYPE_BLOB;
+          break;
+        }
+
+        case ValueType_File:
+        {
+          const std::string& content = dynamic_cast<const FileValue&>(value).GetContent();
+          inputs[i].buffer = const_cast<char*>(content.c_str());
+          inputs[i].buffer_length = content.size();
+          inputs[i].buffer_type = MYSQL_TYPE_BLOB;
+          break;
+        }
+
+        case ValueType_Null:
+        {
+          inputs[i].buffer = NULL;
+          inputs[i].buffer_type = MYSQL_TYPE_NULL;
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+
+    if (!inputs.empty())
+    {
+      db_.CheckErrorCode(mysql_stmt_bind_param(statement_, &inputs[0]));
+    }
+
+    db_.CheckErrorCode(mysql_stmt_execute(statement_));
+
+    outputs_.resize(result_.size());
+
+    for (size_t i = 0; i < result_.size(); i++)
+    {
+      assert(result_[i] != NULL);
+      result_[i]->PrepareBind(outputs_[i]);
+    }
+
+    if (!outputs_.empty())
+    {
+      db_.CheckErrorCode(mysql_stmt_bind_result(statement_, &outputs_[0]));
+      db_.CheckErrorCode(mysql_stmt_store_result(statement_));
+    }
+
+    return new MySQLResult(db_, *this);
+  }
+
+
+  void MySQLStatement::ExecuteWithoutResult(MySQLTransaction& transaction,
+                                            const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> dummy(Execute(transaction, parameters));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLStatement.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_MYSQL != 1
+#  error MySQL support must be enabled to use this file
+#endif
+
+#include "MySQLDatabase.h"
+#include "MySQLTransaction.h"
+#include "../Common/GenericFormatter.h"
+
+namespace OrthancDatabases
+{
+  class MySQLStatement : public IPrecompiledStatement
+  {
+  private:
+    class ResultField;
+    class ResultMetadata;    
+
+    void Close();
+
+    MySQLDatabase&             db_;
+    bool                       readOnly_;
+    MYSQL_STMT                *statement_;
+    GenericFormatter           formatter_;
+    std::vector<ResultField*>  result_;
+    std::vector<MYSQL_BIND>    outputs_;
+
+  public:
+    MySQLStatement(MySQLDatabase& db,
+                   const Query& query);
+
+    virtual ~MySQLStatement()
+    {
+      Close();
+    }
+
+    virtual bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    MYSQL_STMT* GetObject();
+
+    size_t GetResultFieldsCount() const
+    {
+      return result_.size();
+    }
+
+    IValue* FetchResultField(size_t i);
+
+    IResult* Execute(MySQLTransaction& transaction,
+                     const Dictionary& parameters);
+
+    void ExecuteWithoutResult(MySQLTransaction& transaction,
+                              const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLTransaction.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,116 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLTransaction.h"
+
+#include "MySQLStatement.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <memory>
+
+namespace OrthancDatabases
+{
+  MySQLTransaction::MySQLTransaction(MySQLDatabase& db) :
+    db_(db),
+    readOnly_(true),
+    active_(false)
+  {
+    db_.Execute("START TRANSACTION");
+    active_ = true;
+  }
+
+  
+  MySQLTransaction::~MySQLTransaction()
+  {
+    if (active_)
+    {
+      LOG(WARNING) << "An active MySQL transaction was dismissed";
+
+      try
+      {
+        db_.Execute("ROLLBACK");
+      }
+      catch (Orthanc::OrthancException&)
+      {
+      }
+    }
+  }
+
+  
+  void MySQLTransaction::Rollback()
+  {
+    if (active_)
+    {
+      db_.Execute("ROLLBACK");
+      active_ = false;
+      readOnly_ = true;
+    }
+    else
+    {
+      LOG(ERROR) << "MySQL: This transaction is already finished";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  void MySQLTransaction::Commit()
+  {
+    if (active_)
+    {
+      db_.Execute("COMMIT");
+      active_ = false;
+      readOnly_ = true;
+    }
+    else
+    {
+      LOG(ERROR) << "MySQL: This transaction is already finished";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  IResult* MySQLTransaction::Execute(IPrecompiledStatement& statement,
+                                     const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> result(dynamic_cast<MySQLStatement&>(statement).Execute(*this, parameters));
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+    
+    return result.release();
+  }
+
+
+  void MySQLTransaction::ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                              const Dictionary& parameters)
+  {
+    dynamic_cast<MySQLStatement&>(statement).ExecuteWithoutResult(*this, parameters);
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MySQL/MySQLTransaction.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_MYSQL != 1
+#  error MySQL support must be enabled to use this file
+#endif
+
+#include "MySQLDatabase.h"
+#include "../Common/ITransaction.h"
+
+namespace OrthancDatabases
+{
+  class MySQLTransaction : public ITransaction
+  {
+  private:
+    MySQLDatabase&  db_;
+    bool            readOnly_;
+    bool            active_;
+
+  public:
+    MySQLTransaction(MySQLDatabase& db);
+
+    virtual ~MySQLTransaction();
+
+    virtual bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    virtual void Rollback();
+    
+    virtual void Commit();
+
+    virtual IResult* Execute(IPrecompiledStatement& statement,
+                             const Dictionary& parameters);
+
+    virtual void ExecuteWithoutResult(IPrecompiledStatement& transaction,
+                                      const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/GlobalProperties.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,249 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GlobalProperties.h"
+
+#include "../Common/Utf8StringValue.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancDatabases
+{
+  bool LookupGlobalProperty(std::string& target,
+                            IDatabase& db,
+                            ITransaction& transaction,
+                            Orthanc::GlobalProperty property)
+  {
+    Query query("SELECT value FROM GlobalProperties WHERE property=${property}", true);
+    query.SetType("property", ValueType_Integer64);
+
+    std::auto_ptr<IPrecompiledStatement> statement(db.Compile(query));
+
+    Dictionary args;
+    args.SetIntegerValue("property", property);
+
+    std::auto_ptr<IResult> result(transaction.Execute(*statement, args));
+
+    if (result->IsDone())
+    {
+      return false;
+    }
+
+    result->SetExpectedType(0, ValueType_Utf8String);
+
+    ValueType type = result->GetField(0).GetType();
+
+    if (type == ValueType_Null)
+    {
+      return false;
+    }
+    else if (type != ValueType_Utf8String)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+    else
+    {
+      target = dynamic_cast<const Utf8StringValue&>(result->GetField(0)).GetContent();
+      return true;
+    }
+  }
+
+
+  bool LookupGlobalProperty(std::string& target /* out */,
+                            DatabaseManager& manager,
+                            Orthanc::GlobalProperty property)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager,
+      "SELECT value FROM GlobalProperties WHERE property=${property}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("property", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("property", property);
+
+    statement.Execute(args);
+    statement.SetResultFieldType(0, ValueType_Utf8String);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+
+    ValueType type = statement.GetResultField(0).GetType();
+
+    if (type == ValueType_Null)
+    {
+      return false;
+    }
+    else if (type != ValueType_Utf8String)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+    else
+    {
+      target = dynamic_cast<const Utf8StringValue&>(statement.GetResultField(0)).GetContent();
+      return true;
+    }
+  }
+
+
+  void SetGlobalProperty(IDatabase& db,
+                         ITransaction& transaction,
+                         Orthanc::GlobalProperty property,
+                         const std::string& utf8)
+  {
+    if (db.GetDialect() == Dialect_SQLite)
+    {
+      Query query("INSERT OR REPLACE INTO GlobalProperties VALUES (${property}, ${value})", false);
+      query.SetType("property", ValueType_Integer64);
+      query.SetType("value", ValueType_Utf8String);
+      
+      std::auto_ptr<IPrecompiledStatement> statement(db.Compile(query));
+
+      Dictionary args;
+      args.SetIntegerValue("property", static_cast<int>(property));
+      args.SetUtf8Value("value", utf8);
+        
+      transaction.ExecuteWithoutResult(*statement, args);
+    }
+    else
+    {
+      {
+        Query query("DELETE FROM GlobalProperties WHERE property=${property}", false);
+        query.SetType("property", ValueType_Integer64);
+      
+        std::auto_ptr<IPrecompiledStatement> statement(db.Compile(query));
+
+        Dictionary args;
+        args.SetIntegerValue("property", static_cast<int>(property));
+        
+        transaction.ExecuteWithoutResult(*statement, args);
+      }
+
+      {
+        Query query("INSERT INTO GlobalProperties VALUES (${property}, ${value})", false);
+        query.SetType("property", ValueType_Integer64);
+        query.SetType("value", ValueType_Utf8String);
+      
+        std::auto_ptr<IPrecompiledStatement> statement(db.Compile(query));
+
+        Dictionary args;
+        args.SetIntegerValue("property", static_cast<int>(property));
+        args.SetUtf8Value("value", utf8);
+        
+        transaction.ExecuteWithoutResult(*statement, args);
+      }
+    }
+  }
+
+
+  void SetGlobalProperty(DatabaseManager& manager,
+                         Orthanc::GlobalProperty property,
+                         const std::string& utf8)
+  {
+    if (manager.GetDialect() == Dialect_SQLite)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "INSERT OR REPLACE INTO GlobalProperties VALUES (${property}, ${value})");
+        
+      statement.SetParameterType("property", ValueType_Integer64);
+      statement.SetParameterType("value", ValueType_Utf8String);
+        
+      Dictionary args;
+      args.SetIntegerValue("property", static_cast<int>(property));
+      args.SetUtf8Value("value", utf8);
+        
+      statement.Execute(args);
+    }
+    else
+    {
+      {
+        DatabaseManager::CachedStatement statement(
+          STATEMENT_FROM_HERE, manager,
+          "DELETE FROM GlobalProperties WHERE property=${property}");
+        
+        statement.SetParameterType("property", ValueType_Integer64);
+        
+        Dictionary args;
+        args.SetIntegerValue("property", property);
+        
+        statement.Execute(args);
+      }
+
+      {
+        DatabaseManager::CachedStatement statement(
+          STATEMENT_FROM_HERE, manager,
+          "INSERT INTO GlobalProperties VALUES (${property}, ${value})");
+        
+        statement.SetParameterType("property", ValueType_Integer64);
+        statement.SetParameterType("value", ValueType_Utf8String);
+        
+        Dictionary args;
+        args.SetIntegerValue("property", static_cast<int>(property));
+        args.SetUtf8Value("value", utf8);
+        
+        statement.Execute(args);
+      }
+    }
+  }
+
+
+  bool LookupGlobalIntegerProperty(int& target,
+                                   IDatabase& db,
+                                   ITransaction& transaction,
+                                   Orthanc::GlobalProperty property)
+  {
+    std::string value;
+
+    if (LookupGlobalProperty(value, db, transaction, property))
+    {
+      try
+      {
+        target = boost::lexical_cast<int>(value);
+        return true;
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        LOG(ERROR) << "Corrupted PostgreSQL database";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }      
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void SetGlobalIntegerProperty(IDatabase& db,
+                                ITransaction& transaction,
+                                Orthanc::GlobalProperty property,
+                                int value)
+  {
+    SetGlobalProperty(db, transaction, property, boost::lexical_cast<std::string>(value));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/GlobalProperties.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Common/DatabaseManager.h"
+
+#include <OrthancServer/ServerEnumerations.h>
+
+namespace OrthancDatabases
+{
+  bool LookupGlobalProperty(std::string& target /* out */,
+                            IDatabase& db,
+                            ITransaction& transaction,
+                            Orthanc::GlobalProperty property);
+
+  bool LookupGlobalProperty(std::string& target /* out */,
+                            DatabaseManager& manager,
+                            Orthanc::GlobalProperty property);
+
+  void SetGlobalProperty(IDatabase& db,
+                         ITransaction& transaction,
+                         Orthanc::GlobalProperty property,
+                         const std::string& utf8);
+
+  void SetGlobalProperty(DatabaseManager& manager,
+                         Orthanc::GlobalProperty property,
+                         const std::string& utf8);
+
+  bool LookupGlobalIntegerProperty(int& target,
+                                   IDatabase& db,
+                                   ITransaction& transaction,
+                                   Orthanc::GlobalProperty property);
+
+  void SetGlobalIntegerProperty(IDatabase& db,
+                                ITransaction& transaction,
+                                Orthanc::GlobalProperty property,
+                                int value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IndexBackend.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,1580 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IndexBackend.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/Utf8StringValue.h"
+#include "GlobalProperties.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <OrthancServer/ServerEnumerations.h>
+
+
+namespace OrthancDatabases
+{
+  static std::string ConvertWildcardToLike(const std::string& query)
+  {
+    std::string s = query;
+
+    for (size_t i = 0; i < s.size(); i++)
+    {
+      if (s[i] == '*')
+      {
+        s[i] = '%';
+      }
+      else if (s[i] == '?')
+      {
+        s[i] = '_';
+      }
+    }
+
+    return s;
+  }
+
+  
+  int64_t IndexBackend::ReadInteger64(const DatabaseManager::CachedStatement& statement,
+                                      size_t field)
+  {
+    if (statement.IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+
+    const IValue& value = statement.GetResultField(field);
+      
+    switch (value.GetType())
+    {
+      case ValueType_Integer64:
+        return dynamic_cast<const Integer64Value&>(value).GetValue();
+
+      default:
+        //LOG(ERROR) << value.Format();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  int32_t IndexBackend::ReadInteger32(const DatabaseManager::CachedStatement& statement,
+                                      size_t field)
+  {
+    if (statement.IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+
+    int64_t value = ReadInteger64(statement, field);
+
+    if (value != static_cast<int64_t>(static_cast<int32_t>(value)))
+    {
+      LOG(ERROR) << "Integer overflow";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return static_cast<int32_t>(value);
+    }
+  }
+
+    
+  std::string IndexBackend::ReadString(const DatabaseManager::CachedStatement& statement,
+                                       size_t field)
+  {
+    const IValue& value = statement.GetResultField(field);
+      
+    switch (value.GetType())
+    {
+      case ValueType_BinaryString:
+        return dynamic_cast<const BinaryStringValue&>(value).GetContent();
+
+      case ValueType_Utf8String:
+        return dynamic_cast<const Utf8StringValue&>(value).GetContent();
+
+      default:
+        //LOG(ERROR) << value.Format();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+    
+  template <typename T>
+  void IndexBackend::ReadListOfIntegers(std::list<T>& target,
+                                        DatabaseManager::CachedStatement& statement,
+                                        const Dictionary& args)
+  {
+    statement.Execute(args);
+      
+    target.clear();
+
+    if (!statement.IsDone())
+    {
+      if (statement.GetResultFieldsCount() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      
+      statement.SetResultFieldType(0, ValueType_Integer64);
+
+      while (!statement.IsDone())
+      {
+        target.push_back(static_cast<T>(ReadInteger64(statement, 0)));
+        statement.Next();
+      }
+    }
+  }
+
+    
+  void IndexBackend::ReadListOfStrings(std::list<std::string>& target,
+                                       DatabaseManager::CachedStatement& statement,
+                                       const Dictionary& args)
+  {
+    statement.Execute(args);
+
+    target.clear();
+      
+    if (!statement.IsDone())
+    {
+      if (statement.GetResultFieldsCount() != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+      
+      while (!statement.IsDone())
+      {
+        target.push_back(ReadString(statement, 0));
+        statement.Next();
+      }
+    }
+  }
+
+
+  void IndexBackend::ReadChangesInternal(bool& done,
+                                         DatabaseManager::CachedStatement& statement,
+                                         const Dictionary& args,
+                                         uint32_t maxResults)
+  {
+    statement.Execute(args);
+
+    uint32_t count = 0;
+
+    while (count < maxResults &&
+           !statement.IsDone())
+    {
+      GetOutput().AnswerChange(
+        ReadInteger64(statement, 0),
+        ReadInteger32(statement, 1),
+        static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 3)),
+        GetPublicId(ReadInteger64(statement, 2)),
+        ReadString(statement, 4));
+
+      statement.Next();
+      count++;
+    }
+
+    done = (count < maxResults ||
+            statement.IsDone());
+  }
+
+
+  void IndexBackend::ReadExportedResourcesInternal(bool& done,
+                                                   DatabaseManager::CachedStatement& statement,
+                                                   const Dictionary& args,
+                                                   uint32_t maxResults)
+  {
+    statement.Execute(args);
+
+    uint32_t count = 0;
+
+    while (count < maxResults &&
+           !statement.IsDone())
+    {
+      int64_t seq = ReadInteger64(statement, 0);
+      OrthancPluginResourceType resourceType =
+        static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 1));
+      std::string publicId = ReadString(statement, 2);
+
+      GetOutput().AnswerExportedResource(seq, 
+                                         resourceType,
+                                         publicId,
+                                         ReadString(statement, 3),  // modality
+                                         ReadString(statement, 8),  // date
+                                         ReadString(statement, 4),  // patient ID
+                                         ReadString(statement, 5),  // study instance UID
+                                         ReadString(statement, 6),  // series instance UID
+                                         ReadString(statement, 7)); // sop instance UID
+
+      statement.Next();
+      count++;
+    }
+
+    done = (count < maxResults ||
+            statement.IsDone());
+  }
+
+
+  void IndexBackend::ClearDeletedFiles()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "DELETE FROM DeletedFiles");
+
+    statement.Execute();
+  }
+    
+
+  void IndexBackend::ClearDeletedResources()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "DELETE FROM DeletedResources");
+
+    statement.Execute();
+  }
+    
+
+  void IndexBackend::SignalDeletedFiles()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM DeletedFiles");
+
+    statement.SetReadOnly(true);
+    statement.Execute();
+
+    while (!statement.IsDone())
+    {
+      std::string a = ReadString(statement, 0);
+      std::string b = ReadString(statement, 5);
+      std::string c = ReadString(statement, 6);
+
+      GetOutput().SignalDeletedAttachment(a.c_str(),
+                                          ReadInteger32(statement, 1),
+                                          ReadInteger64(statement, 3),
+                                          b.c_str(),
+                                          ReadInteger32(statement, 4),
+                                          ReadInteger64(statement, 2),
+                                          c.c_str());
+
+      statement.Next();
+    }
+  }
+
+
+  void IndexBackend::SignalDeletedResources()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM DeletedResources");
+
+    statement.SetReadOnly(true);
+    statement.Execute();
+
+    while (!statement.IsDone())
+    {
+      GetOutput().SignalDeletedResource(
+        ReadString(statement, 1),
+        static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0)));
+
+      statement.Next();
+    }
+  }
+
+
+  IndexBackend::IndexBackend(IDatabaseFactory* factory) :
+    manager_(factory)
+  {
+  }
+
+    
+  void IndexBackend::AddAttachment(int64_t id,
+                                   const OrthancPluginAttachment& attachment)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "INSERT INTO AttachedFiles VALUES(${id}, ${type}, ${uuid}, "
+      "${compressed}, ${uncompressed}, ${compression}, ${hash}, ${hash-compressed})");
+
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("type", ValueType_Integer64);
+    statement.SetParameterType("uuid", ValueType_Utf8String);
+    statement.SetParameterType("compressed", ValueType_Integer64);
+    statement.SetParameterType("uncompressed", ValueType_Integer64);
+    statement.SetParameterType("compression", ValueType_Integer64);
+    statement.SetParameterType("hash", ValueType_Utf8String);
+    statement.SetParameterType("hash-compressed", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("type", attachment.contentType);
+    args.SetUtf8Value("uuid", attachment.uuid);
+    args.SetIntegerValue("compressed", attachment.compressedSize);
+    args.SetIntegerValue("uncompressed", attachment.uncompressedSize);
+    args.SetIntegerValue("compression", attachment.compressionType);
+    args.SetUtf8Value("hash", attachment.uncompressedHash);
+    args.SetUtf8Value("hash-compressed", attachment.compressedHash);
+    
+    statement.Execute(args);
+  }
+
+    
+  void IndexBackend::AttachChild(int64_t parent,
+                                 int64_t child)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "UPDATE Resources SET parentId = ${parent} WHERE internalId = ${child}");
+
+    statement.SetParameterType("parent", ValueType_Integer64);
+    statement.SetParameterType("child", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("parent", parent);
+    args.SetIntegerValue("child", child);
+    
+    statement.Execute(args);
+  }
+
+    
+  void IndexBackend::ClearChanges()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "DELETE FROM Changes");
+
+    statement.Execute();
+  }
+
+    
+  void IndexBackend::ClearExportedResources()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "DELETE FROM ExportedResources");
+
+    statement.Execute();
+  }
+
+    
+  void IndexBackend::DeleteAttachment(int64_t id,
+                                      int32_t attachment)
+  {
+    ClearDeletedFiles();
+
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "DELETE FROM AttachedFiles WHERE id=${id} AND fileType=${type}");
+
+      statement.SetParameterType("id", ValueType_Integer64);
+      statement.SetParameterType("type", ValueType_Integer64);
+
+      Dictionary args;
+      args.SetIntegerValue("id", id);
+      args.SetIntegerValue("type", static_cast<int>(attachment));
+    
+      statement.Execute(args);
+    }
+
+    SignalDeletedFiles();
+  }
+
+    
+  void IndexBackend::DeleteMetadata(int64_t id,
+                                    int32_t metadataType)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "DELETE FROM Metadata WHERE id=${id} and type=${type}");
+
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("type", static_cast<int>(metadataType));
+    
+    statement.Execute(args);
+  }
+
+    
+  void IndexBackend::DeleteResource(int64_t id)
+  {
+    assert(GetDialect() != Dialect_MySQL);
+    
+    ClearDeletedFiles();
+    ClearDeletedResources();
+    
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "DELETE FROM RemainingAncestor");
+
+      statement.Execute();
+    }
+      
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "DELETE FROM Resources WHERE internalId=${id}");
+
+      statement.SetParameterType("id", ValueType_Integer64);
+
+      Dictionary args;
+      args.SetIntegerValue("id", id);
+    
+      statement.Execute(args);
+    }
+
+
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "SELECT * FROM RemainingAncestor");
+
+      statement.Execute();
+
+      if (!statement.IsDone())
+      {
+        GetOutput().SignalRemainingAncestor(
+          ReadString(statement, 1),
+          static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0)));
+          
+        // There is at most 1 remaining ancestor
+        assert((statement.Next(), statement.IsDone()));
+      }
+    }
+    
+    SignalDeletedFiles();
+    SignalDeletedResources();
+  }
+
+
+  void IndexBackend::GetAllInternalIds(std::list<int64_t>& target,
+                                       OrthancPluginResourceType resourceType)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT internalId FROM Resources WHERE resourceType=${type}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("type", static_cast<int>(resourceType));
+
+    ReadListOfIntegers<int64_t>(target, statement, args);
+  }
+
+    
+  void IndexBackend::GetAllPublicIds(std::list<std::string>& target,
+                                     OrthancPluginResourceType resourceType)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT publicId FROM Resources WHERE resourceType=${type}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("type", static_cast<int>(resourceType));
+
+    ReadListOfStrings(target, statement, args);
+  }
+
+    
+  void IndexBackend::GetAllPublicIds(std::list<std::string>& target,
+                                     OrthancPluginResourceType resourceType,
+                                     uint64_t since,
+                                     uint64_t limit)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT publicId FROM (SELECT publicId FROM Resources "
+      "WHERE resourceType=${type}) AS tmp "
+      "ORDER BY tmp.publicId LIMIT ${limit} OFFSET ${since}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("type", ValueType_Integer64);
+    statement.SetParameterType("limit", ValueType_Integer64);
+    statement.SetParameterType("since", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("type", static_cast<int>(resourceType));
+    args.SetIntegerValue("limit", limit);
+    args.SetIntegerValue("since", since);
+
+    ReadListOfStrings(target, statement, args);
+  }
+
+    
+  /* Use GetOutput().AnswerChange() */
+  void IndexBackend::GetChanges(bool& done /*out*/,
+                                int64_t since,
+                                uint32_t maxResults)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM Changes WHERE seq>${since} ORDER BY seq LIMIT ${limit}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("limit", ValueType_Integer64);
+    statement.SetParameterType("since", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("limit", maxResults + 1);
+    args.SetIntegerValue("since", since);
+
+    ReadChangesInternal(done, statement, args, maxResults);
+  }
+
+    
+  void IndexBackend::GetChildrenInternalId(std::list<int64_t>& target /*out*/,
+                                           int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT a.internalId FROM Resources AS a, Resources AS b  "
+      "WHERE a.parentId = b.internalId AND b.internalId = ${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    ReadListOfIntegers<int64_t>(target, statement, args);
+  }
+
+    
+  void IndexBackend::GetChildrenPublicId(std::list<std::string>& target /*out*/,
+                                         int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT a.publicId FROM Resources AS a, Resources AS b  "
+      "WHERE a.parentId = b.internalId AND b.internalId = ${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    ReadListOfStrings(target, statement, args);
+  }
+
+    
+  /* Use GetOutput().AnswerExportedResource() */
+  void IndexBackend::GetExportedResources(bool& done /*out*/,
+                                          int64_t since,
+                                          uint32_t maxResults)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM ExportedResources WHERE seq>${since} ORDER BY seq LIMIT ${limit}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("limit", ValueType_Integer64);
+    statement.SetParameterType("since", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("limit", maxResults + 1);
+    args.SetIntegerValue("since", since);
+
+    ReadExportedResourcesInternal(done, statement, args, maxResults);
+  }
+
+    
+  /* Use GetOutput().AnswerChange() */
+  void IndexBackend::GetLastChange()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
+
+    statement.SetReadOnly(true);
+      
+    Dictionary args;
+
+    bool done;  // Ignored
+    ReadChangesInternal(done, statement, args, 1);
+  }
+
+    
+  /* Use GetOutput().AnswerExportedResource() */
+  void IndexBackend::GetLastExportedResource()
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
+
+    statement.SetReadOnly(true);
+      
+    Dictionary args;
+
+    bool done;  // Ignored
+    ReadExportedResourcesInternal(done, statement, args, 1);
+  }
+
+    
+  /* Use GetOutput().AnswerDicomTag() */
+  void IndexBackend::GetMainDicomTags(int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM MainDicomTags WHERE id=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    statement.Execute(args);
+
+    while (!statement.IsDone())
+    {
+      GetOutput().AnswerDicomTag(static_cast<uint16_t>(ReadInteger64(statement, 1)),
+                                 static_cast<uint16_t>(ReadInteger64(statement, 2)),
+                                 ReadString(statement, 3));
+      statement.Next();
+    }
+  }
+
+    
+  std::string IndexBackend::GetPublicId(int64_t resourceId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT publicId FROM Resources WHERE internalId=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resourceId);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+    else
+    {
+      return ReadString(statement, 0);
+    }
+  }
+
+    
+  uint64_t IndexBackend::GetResourceCount(OrthancPluginResourceType resourceType)
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    switch (GetDialect())
+    {
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM Resources WHERE resourceType=${type}"));
+        break;
+
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, GetManager(),
+                        "SELECT CAST(COUNT(*) AS BIGINT) FROM Resources WHERE resourceType=${type}"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, GetManager(),
+                        "SELECT COUNT(*) FROM Resources WHERE resourceType=${type}"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    statement->SetReadOnly(true);
+    statement->SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("type", resourceType);
+
+    statement->Execute(args);
+
+    return static_cast<uint64_t>(ReadInteger64(*statement, 0));
+  }
+
+    
+  OrthancPluginResourceType IndexBackend::GetResourceType(int64_t resourceId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT resourceType FROM Resources WHERE internalId=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resourceId);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+    else
+    {
+      return static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 0));
+    }
+  }
+
+    
+  uint64_t IndexBackend::GetTotalCompressedSize()
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    // NB: "COALESCE" is used to replace "NULL" by "0" if the number of rows is empty
+
+    switch (GetDialect())
+    {
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COALESCE(SUM(compressedSize), 0) AS UNSIGNED INTEGER) FROM AttachedFiles"));
+        break;
+        
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COALESCE(SUM(compressedSize), 0) AS BIGINT) FROM AttachedFiles"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT COALESCE(SUM(compressedSize), 0) FROM AttachedFiles"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    statement->SetReadOnly(true);
+    statement->Execute();
+
+    return static_cast<uint64_t>(ReadInteger64(*statement, 0));
+  }
+
+    
+  uint64_t IndexBackend::GetTotalUncompressedSize()
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    // NB: "COALESCE" is used to replace "NULL" by "0" if the number of rows is empty
+
+    switch (GetDialect())
+    {
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COALESCE(SUM(uncompressedSize), 0) AS UNSIGNED INTEGER) FROM AttachedFiles"));
+        break;
+        
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COALESCE(SUM(uncompressedSize), 0) AS BIGINT) FROM AttachedFiles"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT COALESCE(SUM(uncompressedSize), 0) FROM AttachedFiles"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    statement->SetReadOnly(true);
+    statement->Execute();
+
+    return static_cast<uint64_t>(ReadInteger64(*statement, 0));
+  }
+
+    
+  bool IndexBackend::IsExistingResource(int64_t internalId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM Resources WHERE internalId=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", internalId);
+
+    statement.Execute(args);
+
+    return !statement.IsDone();
+  }
+
+    
+  bool IndexBackend::IsProtectedPatient(int64_t internalId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT * FROM PatientRecyclingOrder WHERE patientId = ${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", internalId);
+
+    statement.Execute(args);
+
+    return statement.IsDone();
+  }
+
+    
+  void IndexBackend::ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                           int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT type FROM Metadata WHERE id=${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    ReadListOfIntegers<int32_t>(target, statement, args);
+  }
+
+    
+  void IndexBackend::ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                              int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT fileType FROM AttachedFiles WHERE id=${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    ReadListOfIntegers<int32_t>(target, statement, args);
+  }
+
+    
+  void IndexBackend::LogChange(const OrthancPluginChange& change)
+  {
+    int64_t id;
+    OrthancPluginResourceType type;
+    if (!LookupResource(id, type, change.publicId) ||
+        type != change.resourceType)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+      
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "INSERT INTO Changes VALUES(${}, ${changeType}, ${id}, ${resourceType}, ${date})");
+
+    statement.SetParameterType("changeType", ValueType_Integer64);
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("resourceType", ValueType_Integer64);
+    statement.SetParameterType("date", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("changeType", change.changeType);
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("resourceType", change.resourceType);
+    args.SetUtf8Value("date", change.date);
+
+    statement.Execute(args);
+  }
+
+    
+  void IndexBackend::LogExportedResource(const OrthancPluginExportedResource& resource)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "INSERT INTO ExportedResources VALUES(${}, ${type}, ${publicId}, "
+      "${modality}, ${patient}, ${study}, ${series}, ${instance}, ${date})");
+
+    statement.SetParameterType("type", ValueType_Integer64);
+    statement.SetParameterType("publicId", ValueType_Utf8String);
+    statement.SetParameterType("modality", ValueType_Utf8String);
+    statement.SetParameterType("patient", ValueType_Utf8String);
+    statement.SetParameterType("study", ValueType_Utf8String);
+    statement.SetParameterType("series", ValueType_Utf8String);
+    statement.SetParameterType("instance", ValueType_Utf8String);
+    statement.SetParameterType("date", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("type", resource.resourceType);
+    args.SetUtf8Value("publicId", resource.publicId);
+    args.SetUtf8Value("modality", resource.modality);
+    args.SetUtf8Value("patient", resource.patientId);
+    args.SetUtf8Value("study", resource.studyInstanceUid);
+    args.SetUtf8Value("series", resource.seriesInstanceUid);
+    args.SetUtf8Value("instance", resource.sopInstanceUid);
+    args.SetUtf8Value("date", resource.date);
+
+    statement.Execute(args);
+  }
+
+    
+  /* Use GetOutput().AnswerAttachment() */
+  bool IndexBackend::LookupAttachment(int64_t id,
+                                      int32_t contentType)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT uuid, uncompressedSize, compressionType, compressedSize, "
+      "uncompressedHash, compressedHash FROM AttachedFiles WHERE id=${id} AND fileType=${type}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("type", static_cast<int>(contentType));
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      GetOutput().AnswerAttachment(ReadString(statement, 0),
+                                   contentType,
+                                   ReadInteger64(statement, 1),
+                                   ReadString(statement, 4),
+                                   ReadInteger32(statement, 2),
+                                   ReadInteger64(statement, 3),
+                                   ReadString(statement, 5));
+      return true;
+    }
+  }
+
+    
+  bool IndexBackend::LookupGlobalProperty(std::string& target /*out*/,
+                                          int32_t property)
+  {
+    return ::OrthancDatabases::LookupGlobalProperty(target, manager_, static_cast<Orthanc::GlobalProperty>(property));
+  }
+
+    
+  void IndexBackend::LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                      OrthancPluginResourceType resourceType,
+                                      uint16_t group,
+                                      uint16_t element,
+                                      OrthancPluginIdentifierConstraint constraint,
+                                      const char* value)
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    std::string header =
+      "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE "
+      "d.id = r.internalId AND r.resourceType=${type} AND d.tagGroup=${group} "
+      "AND d.tagElement=${element} AND ";
+      
+    switch (constraint)
+    {
+      case OrthancPluginIdentifierConstraint_Equal:
+        header += "d.value = ${value}";
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager_, header.c_str()));
+        break;
+        
+      case OrthancPluginIdentifierConstraint_SmallerOrEqual:
+        header += "d.value <= ${value}";
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager_, header.c_str()));
+        break;
+        
+      case OrthancPluginIdentifierConstraint_GreaterOrEqual:
+        header += "d.value >= ${value}";
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager_, header.c_str()));
+        break;
+        
+      case OrthancPluginIdentifierConstraint_Wildcard:
+        header += "d.value LIKE ${value}";
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager_, header.c_str()));
+        break;
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+
+    statement->SetReadOnly(true);
+    statement->SetParameterType("type", ValueType_Integer64);
+    statement->SetParameterType("group", ValueType_Integer64);
+    statement->SetParameterType("element", ValueType_Integer64);
+    statement->SetParameterType("value", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("type", resourceType);
+    args.SetIntegerValue("group", group);
+    args.SetIntegerValue("element", element);
+
+    if (constraint == OrthancPluginIdentifierConstraint_Wildcard)
+    {
+      args.SetUtf8Value("value", ConvertWildcardToLike(value));
+    }
+    else
+    {
+      args.SetUtf8Value("value", value);
+    }
+
+    statement->Execute(args);
+
+    target.clear();
+    while (!statement->IsDone())
+    {
+      target.push_back(ReadInteger64(*statement, 0));
+      statement->Next();
+    }
+  }
+
+    
+  void IndexBackend::LookupIdentifierRange(std::list<int64_t>& target /*out*/,
+                                           OrthancPluginResourceType resourceType,
+                                           uint16_t group,
+                                           uint16_t element,
+                                           const char* start,
+                                           const char* end)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE "
+      "d.id = r.internalId AND r.resourceType=${type} AND d.tagGroup=${group} "
+      "AND d.tagElement=${element} AND d.value>=${start} AND d.value<=${end}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("type", ValueType_Integer64);
+    statement.SetParameterType("group", ValueType_Integer64);
+    statement.SetParameterType("element", ValueType_Integer64);
+    statement.SetParameterType("start", ValueType_Utf8String);
+    statement.SetParameterType("end", ValueType_Utf8String);
+    
+    Dictionary args;
+    args.SetIntegerValue("type", resourceType);
+    args.SetIntegerValue("group", group);
+    args.SetIntegerValue("element", element);
+    args.SetUtf8Value("start", start);
+    args.SetUtf8Value("end", end);
+
+    statement.Execute(args);
+
+    target.clear();
+    while (!statement.IsDone())
+    {
+      target.push_back(ReadInteger64(statement, 0));
+      statement.Next();
+    }
+  }
+
+    
+  bool IndexBackend::LookupMetadata(std::string& target /*out*/,
+                                    int64_t id,
+                                    int32_t metadataType)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT value FROM Metadata WHERE id=${id} and type=${type}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("type", metadataType);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      target = ReadString(statement, 0);
+      return true;
+    }
+  } 
+
+    
+  bool IndexBackend::LookupParent(int64_t& parentId /*out*/,
+                                  int64_t resourceId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT parentId FROM Resources WHERE internalId=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resourceId);
+
+    statement.Execute(args);
+
+    if (statement.IsDone() ||
+        statement.GetResultField(0).GetType() == ValueType_Null)
+    {
+      return false;
+    }
+    else
+    {
+      parentId = ReadInteger64(statement, 0);
+      return true;
+    }
+  }
+
+    
+  bool IndexBackend::LookupResource(int64_t& id /*out*/,
+                                    OrthancPluginResourceType& type /*out*/,
+                                    const char* publicId)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT internalId, resourceType FROM Resources WHERE publicId=${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetUtf8Value("id", publicId);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      id = ReadInteger64(statement, 0);
+      type = static_cast<OrthancPluginResourceType>(ReadInteger32(statement, 1));
+      return true;
+    }
+  }
+
+    
+  bool IndexBackend::SelectPatientToRecycle(int64_t& internalId /*out*/)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
+
+    statement.SetReadOnly(true);
+    statement.Execute();
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      internalId = ReadInteger64(statement, 0);
+      return true;
+    }
+  }
+
+    
+  bool IndexBackend::SelectPatientToRecycle(int64_t& internalId /*out*/,
+                                            int64_t patientIdToAvoid)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "SELECT patientId FROM PatientRecyclingOrder "
+      "WHERE patientId != ${id} ORDER BY seq ASC LIMIT 1");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", patientIdToAvoid);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      internalId = ReadInteger64(statement, 0);
+      return true;
+    }
+  }
+
+    
+  void IndexBackend::SetGlobalProperty(int32_t property,
+                                       const char* value)
+  {
+    return ::OrthancDatabases::SetGlobalProperty(manager_, static_cast<Orthanc::GlobalProperty>(property), value);
+  }
+
+
+  static void ExecuteSetTag(DatabaseManager::CachedStatement& statement,
+                            int64_t id,
+                            uint16_t group,
+                            uint16_t element,
+                            const char* value)
+  {
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("group", ValueType_Integer64);
+    statement.SetParameterType("element", ValueType_Integer64);
+    statement.SetParameterType("value", ValueType_Utf8String);
+        
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+    args.SetIntegerValue("group", group);
+    args.SetIntegerValue("element", element);
+    args.SetUtf8Value("value", value);
+        
+    statement.Execute(args);
+  }
+
+    
+  void IndexBackend::SetMainDicomTag(int64_t id,
+                                     uint16_t group,
+                                     uint16_t element,
+                                     const char* value)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "INSERT INTO MainDicomTags VALUES(${id}, ${group}, ${element}, ${value})");
+
+    ExecuteSetTag(statement, id, group, element, value);
+  }
+
+    
+  void IndexBackend::SetIdentifierTag(int64_t id,
+                                      uint16_t group,
+                                      uint16_t element,
+                                      const char* value)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager_,
+      "INSERT INTO DicomIdentifiers VALUES(${id}, ${group}, ${element}, ${value})");
+        
+    ExecuteSetTag(statement, id, group, element, value);
+  } 
+
+    
+  void IndexBackend::SetMetadata(int64_t id,
+                                 int32_t metadataType,
+                                 const char* value)
+  {
+    if (GetDialect() == Dialect_SQLite)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "INSERT OR REPLACE INTO Metadata VALUES (${id}, ${type}, ${value})");
+        
+      statement.SetParameterType("id", ValueType_Integer64);
+      statement.SetParameterType("type", ValueType_Integer64);
+      statement.SetParameterType("value", ValueType_Utf8String);
+        
+      Dictionary args;
+      args.SetIntegerValue("id", id);
+      args.SetIntegerValue("type", metadataType);
+      args.SetUtf8Value("value", value);
+        
+      statement.Execute(args);
+    }
+    else
+    {
+      {
+        DatabaseManager::CachedStatement statement(
+          STATEMENT_FROM_HERE, manager_,
+          "DELETE FROM Metadata WHERE id=${id} AND type=${type}");
+        
+        statement.SetParameterType("id", ValueType_Integer64);
+        statement.SetParameterType("type", ValueType_Integer64);
+        
+        Dictionary args;
+        args.SetIntegerValue("id", id);
+        args.SetIntegerValue("type", metadataType);
+        
+        statement.Execute(args);
+      }
+
+      {
+        DatabaseManager::CachedStatement statement(
+          STATEMENT_FROM_HERE, manager_,
+          "INSERT INTO Metadata VALUES (${id}, ${type}, ${value})");
+        
+        statement.SetParameterType("id", ValueType_Integer64);
+        statement.SetParameterType("type", ValueType_Integer64);
+        statement.SetParameterType("value", ValueType_Utf8String);
+        
+        Dictionary args;
+        args.SetIntegerValue("id", id);
+        args.SetIntegerValue("type", metadataType);
+        args.SetUtf8Value("value", value);
+        
+        statement.Execute(args);
+      }
+    }
+  }
+
+    
+  void IndexBackend::SetProtectedPatient(int64_t internalId, 
+                                         bool isProtected)
+  {
+    if (isProtected)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "DELETE FROM PatientRecyclingOrder WHERE patientId=${id}");
+        
+      statement.SetParameterType("id", ValueType_Integer64);
+        
+      Dictionary args;
+      args.SetIntegerValue("id", internalId);
+        
+      statement.Execute(args);
+    }
+    else if (IsProtectedPatient(internalId))
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "INSERT INTO PatientRecyclingOrder VALUES(${}, ${id})");
+        
+      statement.SetParameterType("id", ValueType_Integer64);
+        
+      Dictionary args;
+      args.SetIntegerValue("id", internalId);
+        
+      statement.Execute(args);
+    }
+    else
+    {
+      // Nothing to do: The patient is already unprotected
+    }
+  }
+
+    
+  uint32_t IndexBackend::GetDatabaseVersion()
+  {
+    std::string version = "unknown";
+      
+    if (LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion))
+    {
+      try
+      {
+        return boost::lexical_cast<unsigned int>(version);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+      }
+    }
+
+    LOG(ERROR) << "The database is corrupted. Drop it manually for Orthanc to recreate it";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+  }
+
+    
+  /**
+   * Upgrade the database to the specified version of the database
+   * schema.  The upgrade script is allowed to make calls to
+   * OrthancPluginReconstructMainDicomTags().
+   **/
+  void IndexBackend::UpgradeDatabase(uint32_t  targetVersion,
+                                     OrthancPluginStorageArea* storageArea)
+  {
+    LOG(ERROR) << "Upgrading database is not implemented by this plugin";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+  }
+
+    
+  void IndexBackend::ClearMainDicomTags(int64_t internalId)
+  {
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "DELETE FROM MainDicomTags WHERE id=${id}");
+        
+      statement.SetParameterType("id", ValueType_Integer64);
+        
+      Dictionary args;
+      args.SetIntegerValue("id", internalId);
+        
+      statement.Execute(args);
+    }
+
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager_,
+        "DELETE FROM DicomIdentifiers WHERE id=${id}");
+        
+      statement.SetParameterType("id", ValueType_Integer64);
+        
+      Dictionary args;
+      args.SetIntegerValue("id", internalId);
+        
+      statement.Execute(args);
+    }
+  }
+
+
+  // For unit testing only!
+  uint64_t IndexBackend::GetResourcesCount()
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    switch (GetDialect())
+    {
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM Resources"));
+        break;
+        
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COUNT(*) AS BIGINT) FROM Resources"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT COUNT(*) FROM Resources"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    statement->SetReadOnly(true);
+    statement->Execute();
+
+    return static_cast<uint64_t>(ReadInteger64(*statement, 0));
+  }    
+
+
+  // For unit testing only!
+  uint64_t IndexBackend::GetUnprotectedPatientsCount()
+  {
+    std::auto_ptr<DatabaseManager::CachedStatement> statement;
+
+    switch (GetDialect())
+    {
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COUNT(*) AS UNSIGNED INT) FROM PatientRecyclingOrder"));
+        break;
+        
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT CAST(COUNT(*) AS BIGINT) FROM PatientRecyclingOrder"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, GetManager(),
+                          "SELECT COUNT(*) FROM PatientRecyclingOrder"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    statement->SetReadOnly(true);
+    statement->Execute();
+
+    return static_cast<uint64_t>(ReadInteger64(*statement, 0));
+  }    
+
+
+  // For unit testing only!
+  bool IndexBackend::GetParentPublicId(std::string& target,
+                                       int64_t id)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "SELECT a.publicId FROM Resources AS a, Resources AS b "
+      "WHERE a.internalId = b.parentId AND b.internalId = ${id}");
+
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    statement.Execute(args);
+
+    if (statement.IsDone())
+    {
+      return false;
+    }
+    else
+    {
+      target = ReadString(statement, 0);
+      return true;
+    }
+  }
+
+
+  // For unit tests only!
+  void IndexBackend::GetChildren(std::list<std::string>& childrenPublicIds,
+                                 int64_t id)
+  { 
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "SELECT publicId FROM Resources WHERE parentId=${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", id);
+
+    ReadListOfStrings(childrenPublicIds, statement, args);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IndexBackend.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,265 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Common/DatabaseManager.h"
+#include "OrthancCppDatabasePlugin.h"
+
+
+namespace OrthancDatabases
+{
+  class IndexBackend : public OrthancPlugins::IDatabaseBackend
+  {
+  private:
+    DatabaseManager   manager_;
+
+  protected:
+    DatabaseManager& GetManager()
+    {
+      return manager_;
+    }
+    
+    static int64_t ReadInteger64(const DatabaseManager::CachedStatement& statement,
+                                 size_t field);
+
+    static int32_t ReadInteger32(const DatabaseManager::CachedStatement& statement,
+                                 size_t field);
+    
+    static std::string ReadString(const DatabaseManager::CachedStatement& statement,
+                                  size_t field);
+    
+    template <typename T>
+    static void ReadListOfIntegers(std::list<T>& target,
+                                   DatabaseManager::CachedStatement& statement,
+                                   const Dictionary& args);
+    
+    static void ReadListOfStrings(std::list<std::string>& target,
+                                  DatabaseManager::CachedStatement& statement,
+                                  const Dictionary& args);
+
+    void ClearDeletedFiles();
+
+    void ClearDeletedResources();
+
+    void SignalDeletedFiles();
+
+    void SignalDeletedResources();
+
+  private:
+    void ReadChangesInternal(bool& done,
+                             DatabaseManager::CachedStatement& statement,
+                             const Dictionary& args,
+                             uint32_t maxResults);
+
+    void ReadExportedResourcesInternal(bool& done,
+                                       DatabaseManager::CachedStatement& statement,
+                                       const Dictionary& args,
+                                       uint32_t maxResults);
+
+  public:
+    IndexBackend(IDatabaseFactory* factory);
+    
+    Dialect GetDialect() const
+    {
+      return manager_.GetDialect();
+    }
+    
+    virtual void Open()
+    {
+      manager_.Open();
+    }
+    
+    virtual void Close()
+    {
+      manager_.Close();
+    }
+    
+    virtual void AddAttachment(int64_t id,
+                               const OrthancPluginAttachment& attachment);
+    
+    virtual void AttachChild(int64_t parent,
+                             int64_t child);
+    
+    virtual void ClearChanges();
+    
+    virtual void ClearExportedResources();
+
+    virtual void DeleteAttachment(int64_t id,
+                                  int32_t attachment);
+    
+    virtual void DeleteMetadata(int64_t id,
+                                int32_t metadataType);
+    
+    virtual void DeleteResource(int64_t id);
+
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   OrthancPluginResourceType resourceType);
+    
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 OrthancPluginResourceType resourceType);
+    
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 OrthancPluginResourceType resourceType,
+                                 uint64_t since,
+                                 uint64_t limit);
+    
+    virtual void GetChanges(bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults);
+    
+    virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
+                                       int64_t id);
+    
+    virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/,
+                                     int64_t id);
+    
+    virtual void GetExportedResources(bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults);
+    
+    virtual void GetLastChange();
+    
+    virtual void GetLastExportedResource();
+    
+    virtual void GetMainDicomTags(int64_t id);
+    
+    virtual std::string GetPublicId(int64_t resourceId);
+    
+    virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType);
+    
+    virtual OrthancPluginResourceType GetResourceType(int64_t resourceId);
+    
+    virtual uint64_t GetTotalCompressedSize();
+    
+    virtual uint64_t GetTotalUncompressedSize();
+    
+    virtual bool IsExistingResource(int64_t internalId);
+    
+    virtual bool IsProtectedPatient(int64_t internalId);
+    
+    virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                       int64_t id);
+    
+    virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                          int64_t id);
+    
+    virtual void LogChange(const OrthancPluginChange& change);
+    
+    virtual void LogExportedResource(const OrthancPluginExportedResource& resource);
+    
+    virtual bool LookupAttachment(int64_t id,
+                                  int32_t contentType);
+    
+    virtual bool LookupGlobalProperty(std::string& target /*out*/,
+                                      int32_t property);
+    
+    virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  OrthancPluginResourceType resourceType,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  OrthancPluginIdentifierConstraint constraint,
+                                  const char* value);
+    
+    virtual void LookupIdentifierRange(std::list<int64_t>& target /*out*/,
+                                       OrthancPluginResourceType resourceType,
+                                       uint16_t group,
+                                       uint16_t element,
+                                       const char* start,
+                                       const char* end);
+
+    virtual bool LookupMetadata(std::string& target /*out*/,
+                                int64_t id,
+                                int32_t metadataType);
+
+    virtual bool LookupParent(int64_t& parentId /*out*/,
+                              int64_t resourceId);
+    
+    virtual bool LookupResource(int64_t& id /*out*/,
+                                OrthancPluginResourceType& type /*out*/,
+                                const char* publicId);
+    
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/);
+    
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/,
+                                        int64_t patientIdToAvoid);
+    
+    virtual void SetGlobalProperty(int32_t property,
+                                   const char* value);
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 uint16_t group,
+                                 uint16_t element,
+                                 const char* value);
+    
+    virtual void SetIdentifierTag(int64_t id,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  const char* value);
+
+    virtual void SetMetadata(int64_t id,
+                             int32_t metadataType,
+                             const char* value);
+    
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected);
+    
+    virtual void StartTransaction()
+    {
+      manager_.StartTransaction();
+    }
+
+    
+    virtual void RollbackTransaction()
+    {
+      manager_.RollbackTransaction();
+    }
+
+    
+    virtual void CommitTransaction()
+    {
+      manager_.CommitTransaction();
+    }
+
+    
+    virtual uint32_t GetDatabaseVersion();
+    
+    virtual void UpgradeDatabase(uint32_t  targetVersion,
+                                 OrthancPluginStorageArea* storageArea);
+    
+    virtual void ClearMainDicomTags(int64_t internalId);
+
+
+    // For unit testing only!
+    virtual uint64_t GetResourcesCount();
+
+    // For unit testing only!
+    virtual uint64_t GetUnprotectedPatientsCount();
+
+    // For unit testing only!
+    virtual bool GetParentPublicId(std::string& target,
+                                   int64_t id);
+
+    // For unit tests only!
+    virtual void GetChildren(std::list<std::string>& childrenPublicIds,
+                             int64_t id);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IndexUnitTests.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,416 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCDatabasePlugin.h>
+#include <OrthancServer/ServerEnumerations.h>
+
+#include <gtest/gtest.h>
+#include <list>
+
+
+static std::auto_ptr<OrthancPluginAttachment>  expectedAttachment;
+static std::list<OrthancPluginDicomTag>  expectedDicomTags;
+static std::auto_ptr<OrthancPluginExportedResource>  expectedExported;
+
+static void CheckAttachment(const OrthancPluginAttachment& attachment)
+{
+  ASSERT_STREQ(expectedAttachment->uuid, attachment.uuid);
+  ASSERT_EQ(expectedAttachment->contentType, attachment.contentType);
+  ASSERT_EQ(expectedAttachment->uncompressedSize, attachment.uncompressedSize);
+  ASSERT_STREQ(expectedAttachment->uncompressedHash, attachment.uncompressedHash);
+  ASSERT_EQ(expectedAttachment->compressionType, attachment.compressionType);
+  ASSERT_EQ(expectedAttachment->compressedSize, attachment.compressedSize);
+  ASSERT_STREQ(expectedAttachment->compressedHash, attachment.compressedHash);
+}
+
+static void CheckExportedResource(const OrthancPluginExportedResource& exported)
+{
+  ASSERT_EQ(expectedExported->seq, exported.seq);
+  ASSERT_EQ(expectedExported->resourceType, exported.resourceType);
+  ASSERT_STREQ(expectedExported->publicId, exported.publicId);
+  ASSERT_STREQ(expectedExported->modality, exported.modality);
+  ASSERT_STREQ(expectedExported->date, exported.date);
+  ASSERT_STREQ(expectedExported->patientId, exported.patientId);
+  ASSERT_STREQ(expectedExported->studyInstanceUid, exported.studyInstanceUid);
+  ASSERT_STREQ(expectedExported->seriesInstanceUid, exported.seriesInstanceUid);
+  ASSERT_STREQ(expectedExported->sopInstanceUid, exported.sopInstanceUid);
+}
+
+static void CheckDicomTag(const OrthancPluginDicomTag& tag)
+{
+  for (std::list<OrthancPluginDicomTag>::const_iterator
+         it = expectedDicomTags.begin(); it != expectedDicomTags.end(); ++it)
+  {
+    if (it->group == tag.group &&
+        it->element == tag.element &&
+        !strcmp(it->value, tag.value))
+    {
+      // OK, match
+      return;
+    }
+  }
+
+  ASSERT_TRUE(0);  // Error
+}
+
+
+
+static OrthancPluginErrorCode InvokeService(struct _OrthancPluginContext_t* context,
+                                            _OrthancPluginService service,
+                                            const void* params)
+{
+  if (service == _OrthancPluginService_DatabaseAnswer)
+  {
+    const _OrthancPluginDatabaseAnswer& answer = 
+      *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(params);
+
+    switch (answer.type)
+    {
+      case _OrthancPluginDatabaseAnswerType_Attachment:
+      {
+        const OrthancPluginAttachment& attachment = 
+          *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
+        CheckAttachment(attachment);
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_ExportedResource:
+      {
+        const OrthancPluginExportedResource& attachment = 
+          *reinterpret_cast<const OrthancPluginExportedResource*>(answer.valueGeneric);
+        CheckExportedResource(attachment);
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_DicomTag:
+      {
+        const OrthancPluginDicomTag& tag = 
+          *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric);
+        CheckDicomTag(tag);
+        break;
+      }
+
+      default:
+        printf("Unhandled message: %d\n", answer.type);
+        break;
+    }
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
+TEST(IndexBackend, Basic)
+{
+  using namespace OrthancDatabases;
+
+  OrthancPluginContext context;
+  context.pluginsManager = NULL;
+  context.orthancVersion = "mainline";
+  context.Free = ::free;
+  context.InvokeService = InvokeService;
+
+#if ORTHANC_ENABLE_POSTGRESQL == 1
+  PostgreSQLIndex db(globalParameters_);
+  db.SetClearAll(true);
+#elif ORTHANC_ENABLE_MYSQL == 1
+  MySQLIndex db(globalParameters_);
+  db.SetClearAll(true);
+#elif ORTHANC_ENABLE_SQLITE == 1
+  SQLiteIndex db;  // Open in memory
+#else
+#  error Unsupported database backend
+#endif
+
+  db.RegisterOutput(new OrthancPlugins::DatabaseBackendOutput(&context, NULL));
+  db.Open();
+
+  std::string s;
+  ASSERT_TRUE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_DatabaseSchemaVersion));
+  ASSERT_EQ("6", s);
+
+  ASSERT_FALSE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_AnonymizationSequence));
+  db.SetGlobalProperty(Orthanc::GlobalProperty_AnonymizationSequence, "Hello");
+  ASSERT_TRUE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ("Hello", s);
+  db.SetGlobalProperty(Orthanc::GlobalProperty_AnonymizationSequence, "HelloWorld");
+  ASSERT_TRUE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_AnonymizationSequence));
+  ASSERT_EQ("HelloWorld", s);
+
+  int64_t a = db.CreateResource("study", OrthancPluginResourceType_Study);
+  ASSERT_TRUE(db.IsExistingResource(a));
+  ASSERT_FALSE(db.IsExistingResource(a + 1));
+
+  int64_t b;
+  OrthancPluginResourceType t;
+  ASSERT_FALSE(db.LookupResource(b, t, "world"));
+  ASSERT_TRUE(db.LookupResource(b, t, "study"));
+  ASSERT_EQ(a, b);
+  ASSERT_EQ(OrthancPluginResourceType_Study, t);
+  
+  b = db.CreateResource("series", OrthancPluginResourceType_Series);
+  ASSERT_NE(a, b);
+
+  ASSERT_EQ("study", db.GetPublicId(a));
+  ASSERT_EQ("series", db.GetPublicId(b));
+  ASSERT_EQ(OrthancPluginResourceType_Study, db.GetResourceType(a));
+  ASSERT_EQ(OrthancPluginResourceType_Series, db.GetResourceType(b));
+
+  db.AttachChild(a, b);
+
+  int64_t c;
+  ASSERT_FALSE(db.LookupParent(c, a));
+  ASSERT_TRUE(db.LookupParent(c, b));
+  ASSERT_EQ(a, c);
+
+  c = db.CreateResource("series2", OrthancPluginResourceType_Series);
+  db.AttachChild(a, c);
+
+  ASSERT_EQ(3u, db.GetResourcesCount());
+  ASSERT_EQ(0u, db.GetResourceCount(OrthancPluginResourceType_Patient));
+  ASSERT_EQ(1u, db.GetResourceCount(OrthancPluginResourceType_Study));
+  ASSERT_EQ(2u, db.GetResourceCount(OrthancPluginResourceType_Series));
+
+  ASSERT_FALSE(db.GetParentPublicId(s, a));
+  ASSERT_TRUE(db.GetParentPublicId(s, b));  ASSERT_EQ("study", s);
+  ASSERT_TRUE(db.GetParentPublicId(s, c));  ASSERT_EQ("study", s);
+
+  std::list<std::string> children;
+  db.GetChildren(children, a);
+  ASSERT_EQ(2u, children.size());
+  db.GetChildren(children, b);
+  ASSERT_EQ(0u, children.size());
+  db.GetChildren(children, c);
+  ASSERT_EQ(0u, children.size());
+
+  std::list<std::string> cp;
+  db.GetChildrenPublicId(cp, a);
+  ASSERT_EQ(2u, cp.size());
+  ASSERT_TRUE(cp.front() == "series" || cp.front() == "series2");
+  ASSERT_TRUE(cp.back() == "series" || cp.back() == "series2");
+  ASSERT_NE(cp.front(), cp.back());
+
+  std::list<std::string> pub;
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Patient);
+  ASSERT_EQ(0u, pub.size());
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Study);
+  ASSERT_EQ(1u, pub.size());
+  ASSERT_EQ("study", pub.front());
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Series);
+  ASSERT_EQ(2u, pub.size());
+  ASSERT_TRUE(pub.front() == "series" || pub.front() == "series2");
+  ASSERT_TRUE(pub.back() == "series" || pub.back() == "series2");
+  ASSERT_NE(pub.front(), pub.back());
+
+  std::list<int64_t> ci;
+  db.GetChildrenInternalId(ci, a);
+  ASSERT_EQ(2u, ci.size());
+  ASSERT_TRUE(ci.front() == b || ci.front() == c);
+  ASSERT_TRUE(ci.back() == b || ci.back() == c);
+  ASSERT_NE(ci.front(), ci.back());
+
+  db.SetMetadata(a, Orthanc::MetadataType_ModifiedFrom, "modified");
+  db.SetMetadata(a, Orthanc::MetadataType_LastUpdate, "update2");
+  ASSERT_FALSE(db.LookupMetadata(s, b, Orthanc::MetadataType_LastUpdate));
+  ASSERT_TRUE(db.LookupMetadata(s, a, Orthanc::MetadataType_LastUpdate));
+  ASSERT_EQ("update2", s);
+  db.SetMetadata(a, Orthanc::MetadataType_LastUpdate, "update");
+  ASSERT_TRUE(db.LookupMetadata(s, a, Orthanc::MetadataType_LastUpdate));
+  ASSERT_EQ("update", s);
+
+  std::list<int32_t> md;
+  db.ListAvailableMetadata(md, a);
+  ASSERT_EQ(2u, md.size());
+  ASSERT_TRUE(md.front() == Orthanc::MetadataType_ModifiedFrom || md.back() == Orthanc::MetadataType_ModifiedFrom);
+  ASSERT_TRUE(md.front() == Orthanc::MetadataType_LastUpdate || md.back() == Orthanc::MetadataType_LastUpdate);
+  std::string mdd;
+  ASSERT_TRUE(db.LookupMetadata(mdd, a, Orthanc::MetadataType_ModifiedFrom));
+  ASSERT_EQ("modified", mdd);
+  ASSERT_TRUE(db.LookupMetadata(mdd, a, Orthanc::MetadataType_LastUpdate));
+  ASSERT_EQ("update", mdd);
+
+  db.ListAvailableMetadata(md, b);
+  ASSERT_EQ(0u, md.size());
+
+  db.DeleteMetadata(a, Orthanc::MetadataType_LastUpdate);
+  db.DeleteMetadata(b, Orthanc::MetadataType_LastUpdate);
+  ASSERT_FALSE(db.LookupMetadata(s, a, Orthanc::MetadataType_LastUpdate));
+
+  db.ListAvailableMetadata(md, a);
+  ASSERT_EQ(1u, md.size());
+  ASSERT_EQ(Orthanc::MetadataType_ModifiedFrom, md.front());
+
+  ASSERT_EQ(0u, db.GetTotalCompressedSize());
+  ASSERT_EQ(0u, db.GetTotalUncompressedSize());
+
+
+  std::list<int32_t> fc;
+
+  OrthancPluginAttachment a1;
+  a1.uuid = "uuid1";
+  a1.contentType = Orthanc::FileContentType_Dicom;
+  a1.uncompressedSize = 42;
+  a1.uncompressedHash = "md5_1";
+  a1.compressionType = Orthanc::CompressionType_None;
+  a1.compressedSize = 42;
+  a1.compressedHash = "md5_1";
+    
+  OrthancPluginAttachment a2;
+  a2.uuid = "uuid2";
+  a2.contentType = Orthanc::FileContentType_DicomAsJson;
+  a2.uncompressedSize = 4242;
+  a2.uncompressedHash = "md5_2";
+  a2.compressionType = Orthanc::CompressionType_None;
+  a2.compressedSize = 4242;
+  a2.compressedHash = "md5_2";
+    
+  db.AddAttachment(a, a1);
+  db.ListAvailableAttachments(fc, a);
+  ASSERT_EQ(1u, fc.size());
+  ASSERT_EQ(Orthanc::FileContentType_Dicom, fc.front());
+  db.AddAttachment(a, a2);
+  db.ListAvailableAttachments(fc, a);
+  ASSERT_EQ(2u, fc.size());
+  ASSERT_FALSE(db.LookupAttachment(b, Orthanc::FileContentType_Dicom));
+
+  ASSERT_EQ(4284u, db.GetTotalCompressedSize());
+  ASSERT_EQ(4284u, db.GetTotalUncompressedSize());
+
+  expectedAttachment.reset(new OrthancPluginAttachment);
+  expectedAttachment->uuid = "uuid1";
+  expectedAttachment->contentType = Orthanc::FileContentType_Dicom;
+  expectedAttachment->uncompressedSize = 42;
+  expectedAttachment->uncompressedHash = "md5_1";
+  expectedAttachment->compressionType = Orthanc::CompressionType_None;
+  expectedAttachment->compressedSize = 42;
+  expectedAttachment->compressedHash = "md5_1";
+  ASSERT_TRUE(db.LookupAttachment(a, Orthanc::FileContentType_Dicom));
+
+  expectedAttachment.reset(new OrthancPluginAttachment);
+  expectedAttachment->uuid = "uuid2";
+  expectedAttachment->contentType = Orthanc::FileContentType_DicomAsJson;
+  expectedAttachment->uncompressedSize = 4242;
+  expectedAttachment->uncompressedHash = "md5_2";
+  expectedAttachment->compressionType = Orthanc::CompressionType_None;
+  expectedAttachment->compressedSize = 4242;
+  expectedAttachment->compressedHash = "md5_2";
+  ASSERT_TRUE(db.LookupAttachment(a, Orthanc::FileContentType_DicomAsJson));
+
+  db.ListAvailableAttachments(fc, b);
+  ASSERT_EQ(0u, fc.size());
+  db.DeleteAttachment(a, Orthanc::FileContentType_Dicom);
+  db.ListAvailableAttachments(fc, a);
+  ASSERT_EQ(1u, fc.size());
+  ASSERT_EQ(Orthanc::FileContentType_DicomAsJson, fc.front());
+  db.DeleteAttachment(a, Orthanc::FileContentType_DicomAsJson);
+  db.ListAvailableAttachments(fc, a);
+  ASSERT_EQ(0u, fc.size());
+
+
+  db.SetIdentifierTag(a, 0x0010, 0x0020, "patient");
+  db.SetIdentifierTag(a, 0x0020, 0x000d, "study");
+
+  expectedDicomTags.clear();
+  expectedDicomTags.push_back(OrthancPluginDicomTag());
+  expectedDicomTags.push_back(OrthancPluginDicomTag());
+  expectedDicomTags.front().group = 0x0010;
+  expectedDicomTags.front().element = 0x0020;
+  expectedDicomTags.front().value = "patient";
+  expectedDicomTags.back().group = 0x0020;
+  expectedDicomTags.back().element = 0x000d;
+  expectedDicomTags.back().value = "study";
+  db.GetMainDicomTags(a);
+
+
+  db.LookupIdentifier(ci, OrthancPluginResourceType_Study, 0x0010, 0x0020, 
+                      OrthancPluginIdentifierConstraint_Equal, "patient");
+  ASSERT_EQ(1u, ci.size());
+  ASSERT_EQ(a, ci.front());
+  db.LookupIdentifier(ci, OrthancPluginResourceType_Study, 0x0010, 0x0020, 
+                      OrthancPluginIdentifierConstraint_Equal, "study");
+  ASSERT_EQ(0u, ci.size());
+
+
+  OrthancPluginExportedResource exp;
+  exp.seq = -1;
+  exp.resourceType = OrthancPluginResourceType_Study;
+  exp.publicId = "id";
+  exp.modality = "remote";
+  exp.date = "date";
+  exp.patientId = "patient";
+  exp.studyInstanceUid = "study";
+  exp.seriesInstanceUid = "series";
+  exp.sopInstanceUid = "instance";
+  db.LogExportedResource(exp);
+
+  expectedExported.reset(new OrthancPluginExportedResource());
+  *expectedExported = exp;
+  expectedExported->seq = 1;
+
+  bool done;
+  db.GetExportedResources(done, 0, 10);
+  
+
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Patient); ASSERT_EQ(0u, pub.size());
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Study); ASSERT_EQ(1u, pub.size());
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Series); ASSERT_EQ(2u, pub.size());
+  db.GetAllPublicIds(pub, OrthancPluginResourceType_Instance); ASSERT_EQ(0u, pub.size());
+  ASSERT_EQ(3u, db.GetResourcesCount());
+
+  ASSERT_EQ(0u, db.GetUnprotectedPatientsCount());  // No patient was inserted
+  ASSERT_TRUE(db.IsExistingResource(c));
+  db.DeleteResource(c);
+  ASSERT_FALSE(db.IsExistingResource(c));
+  ASSERT_TRUE(db.IsExistingResource(a));
+  ASSERT_TRUE(db.IsExistingResource(b));
+  ASSERT_EQ(2u, db.GetResourcesCount());
+  db.DeleteResource(a);
+  ASSERT_EQ(0u, db.GetResourcesCount());
+  ASSERT_FALSE(db.IsExistingResource(a));
+  ASSERT_FALSE(db.IsExistingResource(b));
+  ASSERT_FALSE(db.IsExistingResource(c));
+
+  ASSERT_EQ(0u, db.GetResourcesCount());
+  ASSERT_EQ(0u, db.GetUnprotectedPatientsCount());
+  int64_t p1 = db.CreateResource("patient1", OrthancPluginResourceType_Patient);
+  int64_t p2 = db.CreateResource("patient2", OrthancPluginResourceType_Patient);
+  int64_t p3 = db.CreateResource("patient3", OrthancPluginResourceType_Patient);
+  ASSERT_EQ(3u, db.GetUnprotectedPatientsCount());
+  int64_t r;
+  ASSERT_TRUE(db.SelectPatientToRecycle(r));
+  ASSERT_EQ(p1, r);
+  ASSERT_TRUE(db.SelectPatientToRecycle(r, p1));
+  ASSERT_EQ(p2, r);
+  ASSERT_FALSE(db.IsProtectedPatient(p1));
+  db.SetProtectedPatient(p1, true);
+  ASSERT_TRUE(db.IsProtectedPatient(p1));
+  ASSERT_TRUE(db.SelectPatientToRecycle(r));
+  ASSERT_EQ(p2, r);
+  db.SetProtectedPatient(p1, false);
+  ASSERT_FALSE(db.IsProtectedPatient(p1));
+  ASSERT_TRUE(db.SelectPatientToRecycle(r));
+  ASSERT_EQ(p2, r);
+  db.DeleteResource(p2);
+  ASSERT_TRUE(db.SelectPatientToRecycle(r, p3));
+  ASSERT_EQ(p1, r);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/OrthancCppDatabasePlugin.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,1620 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+/**
+ * NOTE: Until Orthanc 1.4.0, this file was part of the Orthanc source
+ * distribution. This file is now part of "orthanc-databases", in
+ * order to uncouple its evolution from the Orthanc core.
+ **/
+
+#pragma once
+
+#include <orthanc/OrthancCDatabasePlugin.h>
+
+#define ORTHANC_PLUGINS_DATABASE_CATCH_COMMON           \
+  catch (::std::runtime_error& e)                       \
+  {                                                     \
+    LogError(backend, e);                               \
+    return OrthancPluginErrorCode_DatabasePlugin;       \
+  }                                                     \
+  catch (::OrthancPlugins::DatabaseException& e)        \
+  {                                                     \
+    return e.GetErrorCode();                            \
+  }                                                     \
+  catch (...)                                           \
+  {                                                     \
+    backend->GetOutput().LogError("Native exception");  \
+    return OrthancPluginErrorCode_DatabasePlugin;       \
+  }
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include <Core/OrthancException.h>
+#  define ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC                  \
+  catch (::Orthanc::OrthancException& e)                          \
+  {                                                               \
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); \
+  }
+#else
+#  define ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+#endif
+
+
+#include <stdexcept>
+#include <list>
+#include <string>
+
+namespace OrthancPlugins
+{
+//! @cond Doxygen_Suppress
+  // This class mimics "boost::noncopyable"
+  class NonCopyable
+  {
+  private:
+    NonCopyable(const NonCopyable&);
+
+    NonCopyable& operator= (const NonCopyable&);
+
+  protected:
+    NonCopyable()
+    {
+    }
+
+    ~NonCopyable()
+    {
+    }
+  };
+//! @endcond
+
+
+  /**
+   * @ingroup Callbacks
+   **/
+  class DatabaseException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    DatabaseException() : code_(OrthancPluginErrorCode_DatabasePlugin)
+    {
+    }
+
+    DatabaseException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode  GetErrorCode() const
+    {
+      return code_;
+    }
+  };
+
+
+  /**
+   * @ingroup Callbacks
+   **/
+  class DatabaseBackendOutput : public NonCopyable
+  {
+    friend class DatabaseBackendAdapter;
+
+  private:
+    enum AllowedAnswers
+    {
+      AllowedAnswers_All,
+      AllowedAnswers_None,
+      AllowedAnswers_Attachment,
+      AllowedAnswers_Change,
+      AllowedAnswers_DicomTag,
+      AllowedAnswers_ExportedResource
+    };
+
+    OrthancPluginContext*         context_;
+    OrthancPluginDatabaseContext* database_;
+    AllowedAnswers                allowedAnswers_;
+
+    void SetAllowedAnswers(AllowedAnswers allowed)
+    {
+      allowedAnswers_ = allowed;
+    }
+
+  public:
+    DatabaseBackendOutput(OrthancPluginContext*         context,
+                          OrthancPluginDatabaseContext* database) :
+      context_(context),
+      database_(database),
+      allowedAnswers_(AllowedAnswers_All /* for unit tests */)
+    {
+    }
+
+    OrthancPluginContext* GetContext()
+    {
+      return context_;
+    }
+
+    void LogError(const std::string& message)
+    {
+      OrthancPluginLogError(context_, message.c_str());
+    }
+
+    void LogWarning(const std::string& message)
+    {
+      OrthancPluginLogWarning(context_, message.c_str());
+    }
+
+    void LogInfo(const std::string& message)
+    {
+      OrthancPluginLogInfo(context_, message.c_str());
+    }
+
+    void SignalDeletedAttachment(const std::string& uuid,
+                                 int32_t            contentType,
+                                 uint64_t           uncompressedSize,
+                                 const std::string& uncompressedHash,
+                                 int32_t            compressionType,
+                                 uint64_t           compressedSize,
+                                 const std::string& compressedHash)
+    {
+      OrthancPluginAttachment attachment;
+      attachment.uuid = uuid.c_str();
+      attachment.contentType = contentType;
+      attachment.uncompressedSize = uncompressedSize;
+      attachment.uncompressedHash = uncompressedHash.c_str();
+      attachment.compressionType = compressionType;
+      attachment.compressedSize = compressedSize;
+      attachment.compressedHash = compressedHash.c_str();
+
+      OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment);
+    }
+
+    void SignalDeletedResource(const std::string& publicId,
+                               OrthancPluginResourceType resourceType)
+    {
+      OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType);
+    }
+
+    void SignalRemainingAncestor(const std::string& ancestorId,
+                                 OrthancPluginResourceType ancestorType)
+    {
+      OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType);
+    }
+
+    void AnswerAttachment(const std::string& uuid,
+                          int32_t            contentType,
+                          uint64_t           uncompressedSize,
+                          const std::string& uncompressedHash,
+                          int32_t            compressionType,
+                          uint64_t           compressedSize,
+                          const std::string& compressedHash)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_Attachment)
+      {
+        throw std::runtime_error("Cannot answer with an attachment in the current state");
+      }
+
+      OrthancPluginAttachment attachment;
+      attachment.uuid = uuid.c_str();
+      attachment.contentType = contentType;
+      attachment.uncompressedSize = uncompressedSize;
+      attachment.uncompressedHash = uncompressedHash.c_str();
+      attachment.compressionType = compressionType;
+      attachment.compressedSize = compressedSize;
+      attachment.compressedHash = compressedHash.c_str();
+
+      OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment);
+    }
+
+    void AnswerChange(int64_t                    seq,
+                      int32_t                    changeType,
+                      OrthancPluginResourceType  resourceType,
+                      const std::string&         publicId,
+                      const std::string&         date)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_Change)
+      {
+        throw std::runtime_error("Cannot answer with a change in the current state");
+      }
+
+      OrthancPluginChange change;
+      change.seq = seq;
+      change.changeType = changeType;
+      change.resourceType = resourceType;
+      change.publicId = publicId.c_str();
+      change.date = date.c_str();
+
+      OrthancPluginDatabaseAnswerChange(context_, database_, &change);
+    }
+
+    void AnswerDicomTag(uint16_t group,
+                        uint16_t element,
+                        const std::string& value)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_DicomTag)
+      {
+        throw std::runtime_error("Cannot answer with a DICOM tag in the current state");
+      }
+
+      OrthancPluginDicomTag tag;
+      tag.group = group;
+      tag.element = element;
+      tag.value = value.c_str();
+
+      OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag);
+    }
+
+    void AnswerExportedResource(int64_t                    seq,
+                                OrthancPluginResourceType  resourceType,
+                                const std::string&         publicId,
+                                const std::string&         modality,
+                                const std::string&         date,
+                                const std::string&         patientId,
+                                const std::string&         studyInstanceUid,
+                                const std::string&         seriesInstanceUid,
+                                const std::string&         sopInstanceUid)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_ExportedResource)
+      {
+        throw std::runtime_error("Cannot answer with an exported resource in the current state");
+      }
+
+      OrthancPluginExportedResource exported;
+      exported.seq = seq;
+      exported.resourceType = resourceType;
+      exported.publicId = publicId.c_str();
+      exported.modality = modality.c_str();
+      exported.date = date.c_str();
+      exported.patientId = patientId.c_str();
+      exported.studyInstanceUid = studyInstanceUid.c_str();
+      exported.seriesInstanceUid = seriesInstanceUid.c_str();
+      exported.sopInstanceUid = sopInstanceUid.c_str();
+
+      OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported);
+    }
+  };
+
+
+  /**
+   * @ingroup Callbacks
+   **/
+  class IDatabaseBackend : public NonCopyable
+  {
+    friend class DatabaseBackendAdapter;
+
+  private:
+    DatabaseBackendOutput*  output_;
+
+    void Finalize()
+    {
+      if (output_ != NULL)
+      {
+        delete output_;
+        output_ = NULL;
+      }
+    }
+
+  protected:
+    DatabaseBackendOutput& GetOutput()
+    {
+      return *output_;
+    }
+
+  public:
+    IDatabaseBackend() : output_(NULL)
+    {
+    }
+
+    virtual ~IDatabaseBackend()
+    {
+      Finalize();
+    }
+
+    // This takes the ownership
+    void RegisterOutput(DatabaseBackendOutput* output)
+    {
+      Finalize();
+      output_ = output;
+    }
+
+    virtual void Open() = 0;
+
+    virtual void Close() = 0;
+
+    virtual void AddAttachment(int64_t id,
+                               const OrthancPluginAttachment& attachment) = 0;
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) = 0;
+
+    virtual void ClearChanges() = 0;
+
+    virtual void ClearExportedResources() = 0;
+
+    virtual int64_t CreateResource(const char* publicId,
+                                   OrthancPluginResourceType type) = 0;
+
+    virtual void DeleteAttachment(int64_t id,
+                                  int32_t attachment) = 0;
+
+    virtual void DeleteMetadata(int64_t id,
+                                int32_t metadataType) = 0;
+
+    virtual void DeleteResource(int64_t id) = 0;
+
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   OrthancPluginResourceType resourceType) = 0;
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 OrthancPluginResourceType resourceType) = 0;
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 OrthancPluginResourceType resourceType,
+                                 uint64_t since,
+                                 uint64_t limit) = 0;
+
+    /* Use GetOutput().AnswerChange() */
+    virtual void GetChanges(bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) = 0;
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
+                                       int64_t id) = 0;
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/,
+                                     int64_t id) = 0;
+
+    /* Use GetOutput().AnswerExportedResource() */
+    virtual void GetExportedResources(bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) = 0;
+
+    /* Use GetOutput().AnswerChange() */
+    virtual void GetLastChange() = 0;
+
+    /* Use GetOutput().AnswerExportedResource() */
+    virtual void GetLastExportedResource() = 0;
+
+    /* Use GetOutput().AnswerDicomTag() */
+    virtual void GetMainDicomTags(int64_t id) = 0;
+
+    virtual std::string GetPublicId(int64_t resourceId) = 0;
+
+    virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0;
+
+    virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0;
+
+    virtual uint64_t GetTotalCompressedSize() = 0;
+    
+    virtual uint64_t GetTotalUncompressedSize() = 0;
+
+    virtual bool IsExistingResource(int64_t internalId) = 0;
+
+    virtual bool IsProtectedPatient(int64_t internalId) = 0;
+
+    virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                       int64_t id) = 0;
+
+    virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                          int64_t id) = 0;
+
+    virtual void LogChange(const OrthancPluginChange& change) = 0;
+
+    virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0;
+    
+    /* Use GetOutput().AnswerAttachment() */
+    virtual bool LookupAttachment(int64_t id,
+                                  int32_t contentType) = 0;
+
+    virtual bool LookupGlobalProperty(std::string& target /*out*/,
+                                      int32_t property) = 0;
+
+    virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  OrthancPluginResourceType resourceType,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  OrthancPluginIdentifierConstraint constraint,
+                                  const char* value) = 0;
+
+    virtual void LookupIdentifierRange(std::list<int64_t>& target /*out*/,
+                                       OrthancPluginResourceType resourceType,
+                                       uint16_t group,
+                                       uint16_t element,
+                                       const char* start,
+                                       const char* end) = 0;
+
+    virtual bool LookupMetadata(std::string& target /*out*/,
+                                int64_t id,
+                                int32_t metadataType) = 0;
+
+    virtual bool LookupParent(int64_t& parentId /*out*/,
+                              int64_t resourceId) = 0;
+
+    virtual bool LookupResource(int64_t& id /*out*/,
+                                OrthancPluginResourceType& type /*out*/,
+                                const char* publicId) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/,
+                                        int64_t patientIdToAvoid) = 0;
+
+    virtual void SetGlobalProperty(int32_t property,
+                                   const char* value) = 0;
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 uint16_t group,
+                                 uint16_t element,
+                                 const char* value) = 0;
+
+    virtual void SetIdentifierTag(int64_t id,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  const char* value) = 0;
+
+    virtual void SetMetadata(int64_t id,
+                             int32_t metadataType,
+                             const char* value) = 0;
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) = 0;
+
+    virtual void StartTransaction() = 0;
+
+    virtual void RollbackTransaction() = 0;
+
+    virtual void CommitTransaction() = 0;
+
+    virtual uint32_t GetDatabaseVersion() = 0;
+
+    /**
+     * Upgrade the database to the specified version of the database
+     * schema.  The upgrade script is allowed to make calls to
+     * OrthancPluginReconstructMainDicomTags().
+     **/
+    virtual void UpgradeDatabase(uint32_t  targetVersion,
+                                 OrthancPluginStorageArea* storageArea) = 0;
+
+    virtual void ClearMainDicomTags(int64_t internalId) = 0;
+  };
+
+
+
+  /**
+   * @brief Bridge between C and C++ database engines.
+   * 
+   * Class creating the bridge between the C low-level primitives for
+   * custom database engines, and the high-level IDatabaseBackend C++
+   * interface.
+   *
+   * @ingroup Callbacks
+   **/
+  class DatabaseBackendAdapter
+  {
+  private:
+    // This class cannot be instantiated
+    DatabaseBackendAdapter()
+    {
+    }
+
+    static void LogError(IDatabaseBackend* backend,
+                         const std::runtime_error& e)
+    {
+      backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what()));
+    }
+
+
+    static OrthancPluginErrorCode  AddAttachment(void* payload,
+                                                 int64_t id,
+                                                 const OrthancPluginAttachment* attachment)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->AddAttachment(id, *attachment);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+                             
+    static OrthancPluginErrorCode  AttachChild(void* payload,
+                                               int64_t parent,
+                                               int64_t child)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->AttachChild(parent, child);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+                   
+    static OrthancPluginErrorCode  ClearChanges(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->ClearChanges();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+                             
+
+    static OrthancPluginErrorCode  ClearExportedResources(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->ClearExportedResources();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  CreateResource(int64_t* id, 
+                                                  void* payload,
+                                                  const char* publicId,
+                                                  OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *id = backend->CreateResource(publicId, resourceType);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  DeleteAttachment(void* payload,
+                                                    int64_t id,
+                                                    int32_t contentType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteAttachment(id, contentType);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+   
+
+    static OrthancPluginErrorCode  DeleteMetadata(void* payload,
+                                                  int64_t id,
+                                                  int32_t metadataType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteMetadata(id, metadataType);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+   
+
+    static OrthancPluginErrorCode  DeleteResource(void* payload,
+                                                  int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteResource(id);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetAllInternalIds(OrthancPluginDatabaseContext* context,
+                                                     void* payload,
+                                                     OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->GetAllInternalIds(target, resourceType);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetAllPublicIds(OrthancPluginDatabaseContext* context,
+                                                   void* payload,
+                                                   OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<std::string> ids;
+        backend->GetAllPublicIds(ids, resourceType);
+
+        for (std::list<std::string>::const_iterator
+               it = ids.begin(); it != ids.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            it->c_str());
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetAllPublicIdsWithLimit(OrthancPluginDatabaseContext* context,
+                                                            void* payload,
+                                                            OrthancPluginResourceType resourceType,
+                                                            uint64_t since,
+                                                            uint64_t limit)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<std::string> ids;
+        backend->GetAllPublicIds(ids, resourceType, since, limit);
+
+        for (std::list<std::string>::const_iterator
+               it = ids.begin(); it != ids.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            it->c_str());
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetChanges(OrthancPluginDatabaseContext* context,
+                                              void* payload,
+                                              int64_t since,
+                                              uint32_t maxResult)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change);
+
+      try
+      {
+        bool done;
+        backend->GetChanges(done, since, maxResult);
+        
+        if (done)
+        {
+          OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_,
+                                                 backend->GetOutput().database_);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetChildrenInternalId(OrthancPluginDatabaseContext* context,
+                                                         void* payload,
+                                                         int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->GetChildrenInternalId(target, id);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  GetChildrenPublicId(OrthancPluginDatabaseContext* context,
+                                                       void* payload,
+                                                       int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<std::string> ids;
+        backend->GetChildrenPublicId(ids, id);
+
+        for (std::list<std::string>::const_iterator
+               it = ids.begin(); it != ids.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            it->c_str());
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetExportedResources(OrthancPluginDatabaseContext* context,
+                                                        void* payload,
+                                                        int64_t  since,
+                                                        uint32_t  maxResult)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource);
+
+      try
+      {
+        bool done;
+        backend->GetExportedResources(done, since, maxResult);
+
+        if (done)
+        {
+          OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_,
+                                                           backend->GetOutput().database_);
+        }
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  GetLastChange(OrthancPluginDatabaseContext* context,
+                                                 void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change);
+
+      try
+      {
+        backend->GetLastChange();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetLastExportedResource(OrthancPluginDatabaseContext* context,
+                                                           void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource);
+
+      try
+      {
+        backend->GetLastExportedResource();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+    
+               
+    static OrthancPluginErrorCode  GetMainDicomTags(OrthancPluginDatabaseContext* context,
+                                                    void* payload,
+                                                    int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag);
+
+      try
+      {
+        backend->GetMainDicomTags(id);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  GetPublicId(OrthancPluginDatabaseContext* context,
+                                               void* payload,
+                                               int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s = backend->GetPublicId(id);
+        OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                          backend->GetOutput().database_,
+                                          s.c_str());
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetResourceCount(uint64_t* target,
+                                                    void* payload,
+                                                    OrthancPluginResourceType  resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetResourceCount(resourceType);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+                   
+
+    static OrthancPluginErrorCode  GetResourceType(OrthancPluginResourceType* resourceType,
+                                                   void* payload,
+                                                   int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *resourceType = backend->GetResourceType(id);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  GetTotalCompressedSize(uint64_t* target,
+                                                          void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetTotalCompressedSize();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  GetTotalUncompressedSize(uint64_t* target,
+                                                            void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetTotalUncompressedSize();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+                   
+
+    static OrthancPluginErrorCode  IsExistingResource(int32_t* existing,
+                                                      void* payload,
+                                                      int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *existing = backend->IsExistingResource(id);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  IsProtectedPatient(int32_t* isProtected,
+                                                      void* payload,
+                                                      int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *isProtected = backend->IsProtectedPatient(id);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  ListAvailableMetadata(OrthancPluginDatabaseContext* context,
+                                                         void* payload,
+                                                         int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int32_t> target;
+        backend->ListAvailableMetadata(target, id);
+
+        for (std::list<int32_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_,
+                                           backend->GetOutput().database_,
+                                           *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  ListAvailableAttachments(OrthancPluginDatabaseContext* context,
+                                                            void* payload,
+                                                            int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int32_t> target;
+        backend->ListAvailableAttachments(target, id);
+
+        for (std::list<int32_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_,
+                                           backend->GetOutput().database_,
+                                           *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LogChange(void* payload,
+                                             const OrthancPluginChange* change)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->LogChange(*change);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  LogExportedResource(void* payload,
+                                                       const OrthancPluginExportedResource* exported)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->LogExportedResource(*exported);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+          
+         
+    static OrthancPluginErrorCode  LookupAttachment(OrthancPluginDatabaseContext* context,
+                                                    void* payload,
+                                                    int64_t id,
+                                                    int32_t contentType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment);
+
+      try
+      {
+        backend->LookupAttachment(id, contentType);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupGlobalProperty(OrthancPluginDatabaseContext* context,
+                                                        void* payload,
+                                                        int32_t property)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s;
+        if (backend->LookupGlobalProperty(s, property))
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            s.c_str());
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupIdentifier3(OrthancPluginDatabaseContext* context,
+                                                     void* payload,
+                                                     OrthancPluginResourceType resourceType,
+                                                     const OrthancPluginDicomTag* tag,
+                                                     OrthancPluginIdentifierConstraint constraint)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->LookupIdentifier(target, resourceType, tag->group, tag->element, constraint, tag->value);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupIdentifierRange(OrthancPluginDatabaseContext* context,
+                                                         void* payload,
+                                                         OrthancPluginResourceType resourceType,
+                                                         uint16_t group,
+                                                         uint16_t element,
+                                                         const char* start,
+                                                         const char* end)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->LookupIdentifierRange(target, resourceType, group, element, start, end);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupMetadata(OrthancPluginDatabaseContext* context,
+                                                  void* payload,
+                                                  int64_t id,
+                                                  int32_t metadata)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s;
+        if (backend->LookupMetadata(s, id, metadata))
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_, s.c_str());
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupParent(OrthancPluginDatabaseContext* context,
+                                                void* payload,
+                                                int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t parent;
+        if (backend->LookupParent(parent, id))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, parent);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  LookupResource(OrthancPluginDatabaseContext* context,
+                                                  void* payload,
+                                                  const char* publicId)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        OrthancPluginResourceType type;
+        if (backend->LookupResource(id, type, publicId))
+        {
+          OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_,
+                                              backend->GetOutput().database_, 
+                                              id, type);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SelectPatientToRecycle(OrthancPluginDatabaseContext* context,
+                                                          void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        if (backend->SelectPatientToRecycle(id))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, id);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SelectPatientToRecycle2(OrthancPluginDatabaseContext* context,
+                                                           void* payload,
+                                                           int64_t patientIdToAvoid)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        if (backend->SelectPatientToRecycle(id, patientIdToAvoid))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, id);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SetGlobalProperty(void* payload,
+                                                     int32_t property,
+                                                     const char* value)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetGlobalProperty(property, value);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SetMainDicomTag(void* payload,
+                                                   int64_t id,
+                                                   const OrthancPluginDicomTag* tag)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetMainDicomTag(id, tag->group, tag->element, tag->value);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SetIdentifierTag(void* payload,
+                                                    int64_t id,
+                                                    const OrthancPluginDicomTag* tag)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetIdentifierTag(id, tag->group, tag->element, tag->value);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SetMetadata(void* payload,
+                                               int64_t id,
+                                               int32_t metadata,
+                                               const char* value)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetMetadata(id, metadata, value);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode  SetProtectedPatient(void* payload,
+                                                       int64_t id,
+                                                       int32_t isProtected)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetProtectedPatient(id, (isProtected != 0));
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode StartTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->StartTransaction();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode RollbackTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->RollbackTransaction();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode CommitTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->CommitTransaction();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode Open(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->Open();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode Close(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->Close();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode GetDatabaseVersion(uint32_t* version,
+                                                     void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        *version = backend->GetDatabaseVersion();
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+
+    static OrthancPluginErrorCode UpgradeDatabase(void* payload,
+                                                  uint32_t  targetVersion,
+                                                  OrthancPluginStorageArea* storageArea)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        backend->UpgradeDatabase(targetVersion, storageArea);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+    
+    static OrthancPluginErrorCode ClearMainDicomTags(void* payload,
+                                                     int64_t internalId)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        backend->ClearMainDicomTags(internalId);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH_ORTHANC
+      ORTHANC_PLUGINS_DATABASE_CATCH_COMMON
+    }
+
+    
+  public:
+    /**
+     * Register a custom database back-end written in C++.
+     *
+     * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+     * @param backend Your custom database engine.
+     **/
+
+    static void Register(OrthancPluginContext* context,
+                         IDatabaseBackend& backend)
+    {
+      OrthancPluginDatabaseBackend  params;
+      memset(&params, 0, sizeof(params));
+
+      OrthancPluginDatabaseExtensions  extensions;
+      memset(&extensions, 0, sizeof(extensions));
+
+      params.addAttachment = AddAttachment;
+      params.attachChild = AttachChild;
+      params.clearChanges = ClearChanges;
+      params.clearExportedResources = ClearExportedResources;
+      params.createResource = CreateResource;
+      params.deleteAttachment = DeleteAttachment;
+      params.deleteMetadata = DeleteMetadata;
+      params.deleteResource = DeleteResource;
+      params.getAllPublicIds = GetAllPublicIds;
+      params.getChanges = GetChanges;
+      params.getChildrenInternalId = GetChildrenInternalId;
+      params.getChildrenPublicId = GetChildrenPublicId;
+      params.getExportedResources = GetExportedResources;
+      params.getLastChange = GetLastChange;
+      params.getLastExportedResource = GetLastExportedResource;
+      params.getMainDicomTags = GetMainDicomTags;
+      params.getPublicId = GetPublicId;
+      params.getResourceCount = GetResourceCount;
+      params.getResourceType = GetResourceType;
+      params.getTotalCompressedSize = GetTotalCompressedSize;
+      params.getTotalUncompressedSize = GetTotalUncompressedSize;
+      params.isExistingResource = IsExistingResource;
+      params.isProtectedPatient = IsProtectedPatient;
+      params.listAvailableMetadata = ListAvailableMetadata;
+      params.listAvailableAttachments = ListAvailableAttachments;
+      params.logChange = LogChange;
+      params.logExportedResource = LogExportedResource;
+      params.lookupAttachment = LookupAttachment;
+      params.lookupGlobalProperty = LookupGlobalProperty;
+      params.lookupIdentifier = NULL;   // Unused starting with Orthanc 0.9.5 (db v6)
+      params.lookupIdentifier2 = NULL;   // Unused starting with Orthanc 0.9.5 (db v6)
+      params.lookupMetadata = LookupMetadata;
+      params.lookupParent = LookupParent;
+      params.lookupResource = LookupResource;
+      params.selectPatientToRecycle = SelectPatientToRecycle;
+      params.selectPatientToRecycle2 = SelectPatientToRecycle2;
+      params.setGlobalProperty = SetGlobalProperty;
+      params.setMainDicomTag = SetMainDicomTag;
+      params.setIdentifierTag = SetIdentifierTag;
+      params.setMetadata = SetMetadata;
+      params.setProtectedPatient = SetProtectedPatient;
+      params.startTransaction = StartTransaction;
+      params.rollbackTransaction = RollbackTransaction;
+      params.commitTransaction = CommitTransaction;
+      params.open = Open;
+      params.close = Close;
+
+      extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit;
+      extensions.getDatabaseVersion = GetDatabaseVersion;
+      extensions.upgradeDatabase = UpgradeDatabase;
+      extensions.clearMainDicomTags = ClearMainDicomTags;
+      extensions.getAllInternalIds = GetAllInternalIds;    // New in Orthanc 0.9.5 (db v6)
+      extensions.lookupIdentifier3 = LookupIdentifier3;    // New in Orthanc 0.9.5 (db v6)
+
+      bool performanceWarning = true;
+
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 0)
+      extensions.lookupIdentifierRange = LookupIdentifierRange;    // New in Orthanc 1.4.0
+      performanceWarning = false;
+#  endif
+#endif
+
+      if (performanceWarning)
+      {
+        OrthancPluginLogWarning(context, "Performance warning: The database plugin was compiled "
+                                "against an old version of the Orthanc SDK, consider upgrading");
+      }
+
+      OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
+      if (!context)
+      {
+        throw std::runtime_error("Unable to register the database backend");
+      }
+
+      backend.RegisterOutput(new DatabaseBackendOutput(context, database));
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLDatabase.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,200 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLDatabase.h"
+
+#include "PostgreSQLResult.h"
+#include "PostgreSQLStatement.h"
+#include "PostgreSQLTransaction.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+// PostgreSQL includes
+#include <libpq-fe.h>
+#include <c.h>
+#include <catalog/pg_type.h>
+
+
+namespace OrthancDatabases
+{
+  void PostgreSQLDatabase::ThrowException(bool log)
+  {
+    if (log)
+    {
+      LOG(ERROR) << "PostgreSQL error: "
+                 << PQerrorMessage(reinterpret_cast<PGconn*>(pg_));
+    }
+    
+    if (PQstatus(reinterpret_cast<PGconn*>(pg_)) == CONNECTION_OK)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
+    }
+  }
+
+
+  void PostgreSQLDatabase::Close()
+  {
+    if (pg_ != NULL)
+    {
+      LOG(INFO) << "Closing connection to PostgreSQL";
+      PQfinish(reinterpret_cast<PGconn*>(pg_));
+      pg_ = NULL;
+    }
+  }
+
+  
+  void PostgreSQLDatabase::Open()
+  {
+    if (pg_ != NULL)
+    {
+      // Already connected
+      return;
+    }
+
+    std::string s;
+    parameters_.Format(s);
+
+    pg_ = PQconnectdb(s.c_str());
+
+    if (pg_ == NULL ||
+        PQstatus(reinterpret_cast<PGconn*>(pg_)) != CONNECTION_OK)
+    {
+      std::string message;
+
+      if (pg_)
+      {
+        message = PQerrorMessage(reinterpret_cast<PGconn*>(pg_));
+        PQfinish(reinterpret_cast<PGconn*>(pg_));
+        pg_ = NULL;
+      }
+
+      LOG(ERROR) << "PostgreSQL error: " << message;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
+    }
+
+    if (parameters_.HasLock())
+    {
+      PostgreSQLTransaction transaction(*this);
+
+      int32_t lock = 42;  // Some arbitrary constant
+
+      PostgreSQLStatement s(*this, "select pg_try_advisory_lock(" + 
+                            boost::lexical_cast<std::string>(lock) + ");");
+
+      PostgreSQLResult result(s);
+      if (result.IsDone() ||
+          !result.GetBoolean(0))
+      {
+        LOG(ERROR) << "The PostgreSQL database is locked by another instance of Orthanc";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+      }
+
+      transaction.Commit();
+    }
+  }
+
+
+  void PostgreSQLDatabase::Execute(const std::string& sql)
+  {
+    LOG(TRACE) << "PostgreSQL: " << sql;
+    Open();
+
+    PGresult* result = PQexec(reinterpret_cast<PGconn*>(pg_), sql.c_str());
+    if (result == NULL)
+    {
+      ThrowException(true);
+    }
+
+    bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK ||
+               PQresultStatus(result) == PGRES_TUPLES_OK);
+
+    if (ok)
+    {
+      PQclear(result);
+    }
+    else
+    {
+      std::string message = PQresultErrorMessage(result);
+      PQclear(result);
+
+      LOG(ERROR) << "PostgreSQL error: " << message;
+      ThrowException(false);
+    }
+  }
+
+
+  bool PostgreSQLDatabase::DoesTableExist(const char* name)
+  {
+    std::string lower(name);
+    std::transform(lower.begin(), lower.end(), lower.begin(), tolower);
+
+    // http://stackoverflow.com/a/24089729/881731
+
+    PostgreSQLStatement statement(*this, 
+                                  "SELECT 1 FROM pg_catalog.pg_class c "
+                                  "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
+                                  "WHERE n.nspname = 'public' AND c.relkind='r' "
+                                  "AND c.relname=$1", true);
+
+    statement.DeclareInputString(0);
+    statement.BindString(0, lower);
+
+    PostgreSQLResult result(statement);
+    return !result.IsDone();
+  }
+
+
+  void PostgreSQLDatabase::ClearAll()
+  {
+    PostgreSQLTransaction transaction(*this);
+    
+    // Remove all the large objects
+    Execute("SELECT lo_unlink(loid) FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) as loids;");
+
+    // http://stackoverflow.com/a/21247009/881731
+    Execute("DROP SCHEMA public CASCADE;");
+    Execute("CREATE SCHEMA public;");
+    Execute("GRANT ALL ON SCHEMA public TO postgres;");
+    Execute("GRANT ALL ON SCHEMA public TO public;");
+    Execute("COMMENT ON SCHEMA public IS 'standard public schema';");
+
+    transaction.Commit();
+  }
+
+
+  IPrecompiledStatement* PostgreSQLDatabase::Compile(const Query& query)
+  {
+    return new PostgreSQLStatement(*this, query);
+  }
+
+
+  ITransaction* PostgreSQLDatabase::CreateTransaction()
+  {
+    return new PostgreSQLTransaction(*this);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLDatabase.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include "PostgreSQLParameters.h"
+#include "../Common/IDatabase.h"
+
+namespace OrthancDatabases
+{
+  class PostgreSQLDatabase : public IDatabase
+  {
+  private:
+    friend class PostgreSQLStatement;
+    friend class PostgreSQLLargeObject;
+
+    PostgreSQLParameters  parameters_;
+    void*                 pg_;   /* Object of type "PGconn*" */
+
+    void ThrowException(bool log);
+
+    void Close();
+
+  public:
+    PostgreSQLDatabase(const PostgreSQLParameters& parameters) :
+    parameters_(parameters),
+    pg_(NULL)
+    {
+    }
+
+    ~PostgreSQLDatabase()
+    {
+      Close();
+    }
+
+    void Open();
+
+    void Execute(const std::string& sql);
+
+    bool DoesTableExist(const char* name);
+
+    void ClearAll();   // Only for unit tests!
+
+    virtual Dialect GetDialect() const
+    {
+      return Dialect_PostgreSQL;
+    }
+
+    virtual IPrecompiledStatement* Compile(const Query& query);
+
+    virtual ITransaction* CreateTransaction();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLLargeObject.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,226 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+// http://www.postgresql.org/docs/9.1/static/lo-interfaces.html#AEN33102
+
+#include "PostgreSQLLargeObject.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+#include <libpq/libpq-fs.h>
+
+
+namespace OrthancDatabases
+{  
+  void PostgreSQLLargeObject::Create()
+  {
+    PGconn* pg = reinterpret_cast<PGconn*>(database_.pg_);
+
+    oid_ = lo_creat(pg, INV_WRITE);
+    if (oid_ == 0)
+    {
+      LOG(ERROR) << "PostgreSQL: Cannot create a large object";
+      database_.ThrowException(false);
+    }
+  }
+
+
+  void PostgreSQLLargeObject::Write(const void* data, 
+                                    size_t size)
+  {
+    static int MAX_CHUNK_SIZE = 16 * 1024 * 1024;
+
+    PGconn* pg = reinterpret_cast<PGconn*>(database_.pg_);
+
+    int fd = lo_open(pg, oid_, INV_WRITE);
+    if (fd < 0)
+    {
+      database_.ThrowException(true);
+    }
+
+    const char* position = reinterpret_cast<const char*>(data);
+    while (size > 0)
+    {
+      int chunk = (size > static_cast<size_t>(MAX_CHUNK_SIZE) ?
+                   MAX_CHUNK_SIZE : static_cast<int>(size));
+      int nbytes = lo_write(pg, fd, position, chunk);
+      if (nbytes <= 0)
+      {
+        lo_close(pg, fd);
+        database_.ThrowException(true);
+      }
+
+      size -= nbytes;
+      position += nbytes;
+    }
+
+    lo_close(pg, fd);
+  }
+
+
+  PostgreSQLLargeObject::PostgreSQLLargeObject(PostgreSQLDatabase& database,
+                                               const void* data,
+                                               size_t size) : 
+    database_(database)
+  {
+    Create();
+    Write(data, size);
+  }
+
+
+  PostgreSQLLargeObject::PostgreSQLLargeObject(PostgreSQLDatabase& database,
+                                               const std::string& s) : 
+    database_(database)
+  {
+    Create();
+
+    if (s.size() != 0)
+    {
+      Write(s.c_str(), s.size());
+    }
+    else
+    {
+      Write(NULL, 0);
+    }
+  }
+
+
+  class PostgreSQLLargeObject::Reader
+  {
+  private: 
+    PostgreSQLDatabase& database_;
+    int fd_;
+    size_t size_;
+
+  public:
+    Reader(PostgreSQLDatabase& database,
+           const std::string& oid) : 
+      database_(database)
+    {
+      PGconn* pg = reinterpret_cast<PGconn*>(database.pg_);
+      Oid id = boost::lexical_cast<Oid>(oid);
+
+      fd_ = lo_open(pg, id, INV_READ);
+
+      if (fd_ < 0 ||
+          lo_lseek(pg, fd_, 0, SEEK_END) < 0)
+      {
+        LOG(ERROR) << "PostgreSQL: No such large object in the database; "
+                   << "Make sure you use a transaction";
+        database.ThrowException(false);
+      }
+
+      // Get the size of the large object
+      int size = lo_tell(pg, fd_);
+      if (size < 0)
+      {
+        database.ThrowException(true);
+      }
+      size_ = static_cast<size_t>(size);
+
+      // Go to the first byte of the object
+      lo_lseek(pg, fd_, 0, SEEK_SET);
+    }
+
+    ~Reader()
+    {
+      lo_close(reinterpret_cast<PGconn*>(database_.pg_), fd_);
+    }
+
+    size_t GetSize() const
+    {
+      return size_;
+    }
+
+    void Read(char* target)
+    {
+      for (size_t position = 0; position < size_; )
+      {
+        size_t remaining = size_ - position;
+
+        int nbytes = lo_read(reinterpret_cast<PGconn*>(database_.pg_), fd_, target + position, remaining);
+        if (nbytes < 0)
+        {
+          LOG(ERROR) << "PostgreSQL: Unable to read the large object in the database";
+          database_.ThrowException(false);
+        }
+
+        position += static_cast<size_t>(nbytes);
+      }
+    }
+  };
+  
+
+  void PostgreSQLLargeObject::Read(std::string& target,
+                                   PostgreSQLDatabase& database,
+                                   const std::string& oid)
+  {
+    Reader reader(database, oid);
+    target.resize(reader.GetSize());    
+
+    if (target.size() > 0)
+    {
+      reader.Read(&target[0]);
+    }
+  }
+
+
+  void PostgreSQLLargeObject::Read(void*& target,
+                                   size_t& size,
+                                   PostgreSQLDatabase& database,
+                                   const std::string& oid)
+  {
+    Reader reader(database, oid);
+    size = reader.GetSize();
+
+    if (size == 0)
+    {
+      target = NULL;
+    }
+    else
+    {
+      target = malloc(size);
+      reader.Read(reinterpret_cast<char*>(target));
+    }
+  }
+
+
+  std::string PostgreSQLLargeObject::GetOid() const
+  {
+    return boost::lexical_cast<std::string>(oid_);
+  }
+
+
+  void PostgreSQLLargeObject::Delete(PostgreSQLDatabase& database,
+                                     const std::string& oid)
+  {
+    PGconn* pg = reinterpret_cast<PGconn*>(database.pg_);
+    Oid id = boost::lexical_cast<Oid>(oid);
+
+    if (lo_unlink(pg, id) < 0)
+    {
+      LOG(ERROR) << "PostgreSQL: Unable to delete the large object from the database";
+      database.ThrowException(false);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLLargeObject.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include "PostgreSQLDatabase.h"
+
+#include <libpq-fe.h>
+
+namespace OrthancDatabases
+{  
+  class PostgreSQLLargeObject : public boost::noncopyable
+  {
+  private:
+    class Reader;
+
+    PostgreSQLDatabase& database_;
+    Oid oid_;
+
+    void Create();
+
+    void Write(const void* data, 
+               size_t size);
+
+  public:
+    PostgreSQLLargeObject(PostgreSQLDatabase& database,
+                          const void* data,
+                          size_t size);
+
+    PostgreSQLLargeObject(PostgreSQLDatabase& database,
+                          const std::string& s);
+
+    std::string GetOid() const;
+
+    static void Read(std::string& target,
+                     PostgreSQLDatabase& database,
+                     const std::string& oid);
+
+    static void Read(void*& target,
+                     size_t& size,
+                     PostgreSQLDatabase& database,
+                     const std::string& oid);
+
+    static void Delete(PostgreSQLDatabase& database,
+                       const std::string& oid);
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLParameters.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,193 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLParameters.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace OrthancDatabases
+{
+  void PostgreSQLParameters::Reset()
+  {
+    host_ = "localhost";
+    port_ = 5432;
+    username_ = "";
+    password_ = "";
+    database_.clear();
+    uri_.clear();
+    lock_ = true;
+  }
+
+
+  PostgreSQLParameters::PostgreSQLParameters()
+  {
+    Reset();
+  }
+
+
+  PostgreSQLParameters::PostgreSQLParameters(const OrthancPlugins::OrthancConfiguration& configuration)
+  {
+    Reset();
+
+    std::string s;
+
+    if (configuration.LookupStringValue(s, "ConnectionUri"))
+    {
+      SetConnectionUri(s);
+    }
+    else
+    {
+      if (configuration.LookupStringValue(s, "Host"))
+      {
+        SetHost(s);
+      }
+
+      unsigned int port;
+      if (configuration.LookupUnsignedIntegerValue(port, "Port"))
+      {
+        SetPortNumber(port);
+      }
+
+      if (configuration.LookupStringValue(s, "Database"))
+      {
+        SetDatabase(s);
+      }
+
+      if (configuration.LookupStringValue(s, "Username"))
+      {
+        SetUsername(s);
+      }
+
+      if (configuration.LookupStringValue(s, "Password"))
+      {
+        SetPassword(s);
+      }
+    }
+
+    lock_ = configuration.GetBooleanValue("Lock", true);  // Use locking by default
+  }
+
+
+  void PostgreSQLParameters::SetConnectionUri(const std::string& uri)
+  {
+    uri_ = uri;
+  }
+
+
+  std::string PostgreSQLParameters::GetConnectionUri() const
+  {
+    if (uri_.empty())
+    {
+      std::string actualUri = "postgresql://";
+
+      if (!username_.empty())
+      {
+        actualUri += username_;
+
+        if (!password_.empty())
+        {
+          actualUri += ":" + password_;
+        }
+
+        actualUri += "@" + host_;
+      }
+      else
+      {
+        actualUri += host_;
+      }
+      
+      if (port_ > 0)
+      {
+        actualUri += ":" + boost::lexical_cast<std::string>(port_);
+      }
+
+      actualUri += "/" + database_;
+
+      return actualUri;
+    }
+    else
+    {
+      return uri_;
+    }
+  }
+
+
+  void PostgreSQLParameters::SetHost(const std::string& host)
+  {
+    uri_.clear();
+    host_ = host;
+  }
+
+  void PostgreSQLParameters::SetPortNumber(unsigned int port)
+  {
+    if (port <= 0 ||
+        port >= 65535)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    uri_.clear();
+    port_ = port;
+  }
+
+  void PostgreSQLParameters::SetUsername(const std::string& username)
+  {
+    uri_.clear();
+    username_ = username;
+  }
+
+  void PostgreSQLParameters::SetPassword(const std::string& password)
+  {
+    uri_.clear();
+    password_ = password;
+  }
+
+  void PostgreSQLParameters::SetDatabase(const std::string& database)
+  {
+    uri_.clear();
+    database_ = database;
+  }
+
+  void PostgreSQLParameters::Format(std::string& target) const
+  {
+    if (uri_.empty())
+    {
+      target = std::string("sslmode=disable") +  // TODO WHY SSL DOES NOT WORK? ("SSL error: wrong version number")
+        " user=" + username_ + 
+        " password=" + password_ + 
+        " host=" + host_ + 
+        " port=" + boost::lexical_cast<std::string>(port_);
+
+      if (database_.size() > 0)
+      {
+        target += " dbname=" + database_;
+      }
+    }
+    else
+    {
+      target = uri_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLParameters.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,106 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+namespace OrthancDatabases
+{
+  class PostgreSQLParameters
+  {
+  private:
+    std::string  host_;
+    uint16_t     port_;
+    std::string  username_;
+    std::string  password_;
+    std::string  database_;
+    std::string  uri_;
+    bool         lock_;
+
+    void Reset();
+
+  public:
+    PostgreSQLParameters();
+
+    PostgreSQLParameters(const OrthancPlugins::OrthancConfiguration& configuration);
+
+    void SetConnectionUri(const std::string& uri);
+
+    std::string GetConnectionUri() const;
+
+    void SetHost(const std::string& host);
+
+    const std::string& GetHost() const
+    {
+      return host_;
+    }
+
+    void SetPortNumber(unsigned int port);
+
+    uint16_t GetPortNumber() const
+    {
+      return port_;
+    }
+
+    void SetUsername(const std::string& username);
+
+    const std::string& GetUsername() const
+    {
+      return username_;
+    }
+
+    void SetPassword(const std::string& password);
+
+    const std::string& GetPassword() const
+    {
+      return password_;
+    }
+
+    void SetDatabase(const std::string& database);
+
+    void ResetDatabase()
+    {
+      SetDatabase("");
+    }
+
+    const std::string& GetDatabase() const
+    {
+      return database_;
+    }
+
+    void SetLock(bool lock)
+    {
+      lock_ = lock;
+    }
+
+    bool HasLock() const
+    {
+      return lock_;
+    }
+
+    void Format(std::string& target) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLResult.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,233 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLResult.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/Utf8StringValue.h"
+
+#include <Core/OrthancException.h>
+#include <Core/Logging.h>
+
+#include <cassert>
+#include <boost/lexical_cast.hpp>
+
+// PostgreSQL includes
+#include <libpq-fe.h>
+#include <c.h>
+#include <catalog/pg_type.h>
+
+#include <Core/Endianness.h>
+
+
+namespace OrthancDatabases
+{
+  void PostgreSQLResult::Clear()
+  {
+    if (result_ != NULL)
+    {
+      PQclear(reinterpret_cast<PGresult*>(result_));
+      result_ = NULL;
+    }
+  }
+
+
+  void PostgreSQLResult::CheckDone()
+  {
+    if (position_ >= PQntuples(reinterpret_cast<PGresult*>(result_)))
+    {
+      // We are at the end of the result set
+      Clear();
+    }
+  }
+
+
+  void PostgreSQLResult::CheckColumn(unsigned int column, unsigned int /*Oid*/ expectedType) const
+  {
+    if (IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (column >= static_cast<unsigned int>(PQnfields(reinterpret_cast<PGresult*>(result_))))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (expectedType != 0 &&
+        expectedType != PQftype(reinterpret_cast<PGresult*>(result_), column))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+  }
+
+
+  PostgreSQLResult::PostgreSQLResult(PostgreSQLStatement& statement) : 
+    position_(0), 
+    database_(statement.GetDatabase())
+  {
+    result_ = statement.Execute();
+    assert(result_ != NULL);   // An exception would have been thrown otherwise
+
+    // This is the first call to "Next()"
+    if (PQresultStatus(reinterpret_cast<PGresult*>(result_)) == PGRES_TUPLES_OK)
+    {
+      CheckDone();
+      columnsCount_ = static_cast<unsigned int>(PQnfields(reinterpret_cast<PGresult*>(result_)));
+    }
+    else
+    {
+      // This is not a SELECT request, we're done
+      Clear();
+      columnsCount_ = 0;
+    }
+  }
+
+
+  void PostgreSQLResult::Next()
+  {
+    position_++;
+    CheckDone();
+  }
+
+
+  bool PostgreSQLResult::IsNull(unsigned int column) const
+  {
+    CheckColumn(column, 0);
+    return PQgetisnull(reinterpret_cast<PGresult*>(result_), position_, column) != 0;
+  }
+
+
+  bool PostgreSQLResult::GetBoolean(unsigned int column) const
+  {
+    CheckColumn(column, BOOLOID);
+    assert(PQfsize(reinterpret_cast<PGresult*>(result_), column) == 1);
+    const uint8_t *v = reinterpret_cast<const uint8_t*>
+      (PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column));
+    return (v[0] != 0);
+  }
+
+
+  int PostgreSQLResult::GetInteger(unsigned int column) const
+  {
+    CheckColumn(column, INT4OID);
+    assert(PQfsize(reinterpret_cast<PGresult*>(result_), column) == 4);
+    char *v = PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column);
+    return htobe32(*reinterpret_cast<int32_t*>(v));
+  }
+
+
+  int64_t PostgreSQLResult::GetInteger64(unsigned int column) const
+  {
+    CheckColumn(column, INT8OID);
+    assert(PQfsize(reinterpret_cast<PGresult*>(result_), column) == 8);
+    char *v = PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column);
+    return htobe64(*reinterpret_cast<int64_t*>(v));
+  }
+
+
+  std::string PostgreSQLResult::GetString(unsigned int column) const
+  {
+    CheckColumn(column, 0);
+
+    Oid oid = PQftype(reinterpret_cast<PGresult*>(result_), column);
+    if (oid != TEXTOID && oid != VARCHAROID && oid != BYTEAOID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    return std::string(PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column));
+  }
+
+
+  void PostgreSQLResult::GetLargeObject(std::string& result,
+                                        unsigned int column) const
+  {
+    CheckColumn(column, OIDOID);
+
+    Oid oid;
+    assert(PQfsize(reinterpret_cast<PGresult*>(result_), column) == sizeof(oid));
+
+    oid = *(const Oid*) PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column);
+    oid = ntohl(oid);
+
+    PostgreSQLLargeObject::Read(result, database_, boost::lexical_cast<std::string>(oid));
+  }
+
+
+  void PostgreSQLResult::GetLargeObject(void*& result,
+                                           size_t& size,
+                                           unsigned int column) const
+  {
+    CheckColumn(column, OIDOID);
+
+    Oid oid;
+    assert(PQfsize(reinterpret_cast<PGresult*>(result_), column) == sizeof(oid));
+
+    oid = *(const Oid*) PQgetvalue(reinterpret_cast<PGresult*>(result_), position_, column);
+    oid = ntohl(oid);
+
+    PostgreSQLLargeObject::Read(result, size, database_, boost::lexical_cast<std::string>(oid));
+  }
+
+
+  IValue* PostgreSQLResult::GetValue(unsigned int column) const
+  {
+    if (IsNull(column))
+    {
+      return new NullValue;
+    }
+
+    Oid type = PQftype(reinterpret_cast<PGresult*>(result_), column);
+
+    switch (type)
+    {
+      case BOOLOID:
+        // Convert Boolean values as integers
+        return new Integer64Value(GetBoolean(column) ? 1 : 0);
+
+      case INT4OID:
+        return new Integer64Value(GetInteger(column));
+
+      case INT8OID:
+        return new Integer64Value(GetInteger64(column));
+
+      case TEXTOID:
+      case VARCHAROID:
+        return new Utf8StringValue(GetString(column));
+
+      case BYTEAOID:
+        return new BinaryStringValue(GetString(column));
+
+      case OIDOID:
+      {
+        std::auto_ptr<BinaryStringValue> value(new BinaryStringValue);
+        GetLargeObject(value->GetContent(), column);
+        return value.release();
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLResult.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include "PostgreSQLStatement.h"
+
+namespace OrthancDatabases
+{
+  class PostgreSQLResult : public boost::noncopyable
+  {
+  private:
+    void                *result_;  /* Object of type "PGresult*" */
+    int                  position_;
+    PostgreSQLDatabase&  database_;
+    unsigned int         columnsCount_;
+
+    void Clear();
+
+    void CheckDone();
+
+    void CheckColumn(unsigned int column, /*Oid*/ unsigned int expectedType) const;
+
+  public:
+    explicit PostgreSQLResult(PostgreSQLStatement& statement);
+
+    ~PostgreSQLResult()
+    {
+      Clear();
+    }
+
+    void Next();
+
+    bool IsDone() const
+    {
+      return result_ == NULL;
+    }
+
+    unsigned int GetColumnsCount() const
+    {
+      return columnsCount_;
+    }
+
+    bool IsNull(unsigned int column) const;
+
+    bool GetBoolean(unsigned int column) const;
+
+    int GetInteger(unsigned int column) const;
+
+    int64_t GetInteger64(unsigned int column) const;
+
+    std::string GetString(unsigned int column) const;
+
+    void GetLargeObject(std::string& result,
+                        unsigned int column) const;
+
+    void GetLargeObject(void*& result,
+                        size_t& size,
+                        unsigned int column) const;
+
+    IValue* GetValue(unsigned int column) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLStatement.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,541 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLStatement.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/FileValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/ResultBase.h"
+#include "../Common/Utf8StringValue.h"
+#include "PostgreSQLResult.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <cassert>
+
+// PostgreSQL includes
+#include <libpq-fe.h>
+#include <c.h>
+#include <catalog/pg_type.h>
+
+#include <Core/Endianness.h>
+
+
+namespace OrthancDatabases
+{
+  class PostgreSQLStatement::Inputs : public boost::noncopyable
+  {
+  private:
+    std::vector<char*> values_;
+    std::vector<int> sizes_;
+
+    static char* Allocate(const void* source, int size)
+    {
+      if (size == 0)
+      {
+        return NULL;
+      }
+      else
+      {
+        char* ptr = reinterpret_cast<char*>(malloc(size));
+
+        if (source != NULL)
+        {
+          memcpy(ptr, source, size);
+        }
+
+        return ptr;
+      }
+    }
+
+    void Resize(size_t size)
+    {
+      // Shrinking of the vector
+      for (size_t i = size; i < values_.size(); i++)
+      {
+        if (values_[i] != NULL)
+          free(values_[i]);
+      }
+
+      values_.resize(size, NULL);
+      sizes_.resize(size, 0);
+    }
+
+    void EnlargeForIndex(size_t index)
+    {
+      if (index >= values_.size())
+      {
+        // The vector is too small
+        Resize(index + 1);
+      }
+    }
+
+  public:
+    Inputs()
+    {
+    }
+
+    ~Inputs()
+    {
+      Resize(0);
+    }
+
+    void SetItem(size_t pos, const void* source, int size)
+    {
+      EnlargeForIndex(pos);
+
+      if (sizes_[pos] == size)
+      {
+        if (source && size != 0)
+        {
+          memcpy(values_[pos], source, size);
+        }
+      }
+      else
+      {
+        if (values_[pos] != NULL)
+        {
+          free(values_[pos]);
+        }
+
+        values_[pos] = Allocate(source, size);
+        sizes_[pos] = size;
+      }
+    }
+
+    void SetItem(size_t pos, int size)
+    {
+      SetItem(pos, NULL, size);
+    }
+
+    void* GetItem(size_t pos) const
+    {
+      if (pos >= values_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      return values_[pos];
+    }
+
+    const std::vector<char*>& GetValues() const
+    {
+      return values_;
+    }
+
+    const std::vector<int>& GetSizes() const
+    {
+      return sizes_;
+    }
+  };
+
+
+  void PostgreSQLStatement::Prepare()
+  {
+    if (id_.size() > 0)
+    {
+      // Already prepared
+      return;
+    }
+
+    for (size_t i = 0; i < oids_.size(); i++)
+    {
+      if (oids_[i] == 0)
+      {
+        // The type of an input parameter was not set
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    id_ = Orthanc::Toolbox::GenerateUuid();
+
+    const unsigned int* tmp = oids_.size() ? &oids_[0] : NULL;
+
+    PGresult* result = PQprepare(reinterpret_cast<PGconn*>(database_.pg_),
+                                 id_.c_str(), sql_.c_str(), oids_.size(), tmp);
+
+    if (result == NULL)
+    {
+      id_.clear();
+      database_.ThrowException(true);
+    }
+
+    bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK);
+    if (ok)
+    {
+      PQclear(result);
+    }
+    else
+    {
+      std::string message = PQresultErrorMessage(result);
+      PQclear(result);
+      id_.clear();
+      LOG(ERROR) << "PostgreSQL error: " << message;
+      database_.ThrowException(false);
+    }
+  }
+
+
+  void PostgreSQLStatement::Unprepare()
+  {
+    if (id_.size() > 0)
+    {
+      // "Although there is no libpq function for deleting a
+      // prepared statement, the SQL DEALLOCATE statement can be
+      // used for that purpose."
+      //database_.Execute("DEALLOCATE " + id_);
+    }
+
+    id_.clear();
+  }
+
+
+  void PostgreSQLStatement::DeclareInputInternal(unsigned int param,
+                                                 unsigned int /*Oid*/ type)
+  {
+    Unprepare();
+
+    if (oids_.size() <= param)
+    {
+      oids_.resize(param + 1, 0);
+      binary_.resize(param + 1);
+    }
+
+    oids_[param] = type;
+    binary_[param] = (type == TEXTOID || type == BYTEAOID || type == OIDOID) ? 0 : 1;
+  }
+
+
+  void PostgreSQLStatement::DeclareInputInteger(unsigned int param)
+  {
+    DeclareInputInternal(param, INT4OID);
+  }
+    
+
+  void PostgreSQLStatement::DeclareInputInteger64(unsigned int param)
+  {
+    DeclareInputInternal(param, INT8OID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputString(unsigned int param)
+  {
+    DeclareInputInternal(param, TEXTOID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputBinary(unsigned int param)
+  {
+    DeclareInputInternal(param, BYTEAOID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputLargeObject(unsigned int param)
+  {
+    DeclareInputInternal(param, OIDOID);
+  }
+
+
+  void* /* PGresult* */ PostgreSQLStatement::Execute()
+  {
+    Prepare();
+
+    PGresult* result;
+
+    if (oids_.size() == 0)
+    {
+      // No parameter
+      result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_),
+                              id_.c_str(), 0, NULL, NULL, NULL, 1);
+    }
+    else
+    {
+      // At least 1 parameter
+      result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_),
+                              id_.c_str(),
+                              oids_.size(),
+                              &inputs_->GetValues()[0],
+                              &inputs_->GetSizes()[0],
+                              &binary_[0],
+                              1);
+    }
+
+    if (result == NULL)
+    {
+      database_.ThrowException(true);
+    }
+
+    return result;
+  }
+
+
+  PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database,
+                                           const std::string& sql,
+                                           bool readOnly) :
+    database_(database),
+    readOnly_(readOnly),
+    sql_(sql),
+    inputs_(new Inputs),
+    formatter_(Dialect_PostgreSQL)
+  {
+    LOG(TRACE) << "PostgreSQL: " << sql;
+  }
+
+
+  PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database,
+                                           const Query& query) :
+    database_(database),
+    readOnly_(query.IsReadOnly()),
+    inputs_(new Inputs),
+    formatter_(Dialect_PostgreSQL)
+  {
+    query.Format(sql_, formatter_);
+    LOG(TRACE) << "PostgreSQL: " << sql_;
+
+    for (size_t i = 0; i < formatter_.GetParametersCount(); i++)
+    {
+      switch (formatter_.GetParameterType(i))
+      {
+        case ValueType_Integer64:
+          DeclareInputInteger64(i);
+          break;
+
+        case ValueType_Utf8String:
+          DeclareInputString(i);
+          break;
+
+        case ValueType_BinaryString:
+          DeclareInputBinary(i);
+          break;
+
+        case ValueType_File:
+          DeclareInputLargeObject(i);
+          break;
+
+        case ValueType_Null:
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  void PostgreSQLStatement::Run()
+  {
+    PGresult* result = reinterpret_cast<PGresult*>(Execute());
+    assert(result != NULL);   // An exception would have been thrown otherwise
+
+    bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK ||
+               PQresultStatus(result) == PGRES_TUPLES_OK);
+    if (ok)
+    {
+      PQclear(result);
+    }
+    else
+    {
+      std::string error = PQresultErrorMessage(result);
+      PQclear(result);
+      LOG(ERROR) << "PostgreSQL error: " << error;
+      database_.ThrowException(false);
+    }
+  }
+
+
+  void PostgreSQLStatement::BindNull(unsigned int param)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    inputs_->SetItem(param, 0);
+  }
+
+
+  void PostgreSQLStatement::BindInteger(unsigned int param,
+                                        int value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != INT4OID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    assert(sizeof(int32_t) == 4);
+    int32_t v = htobe32(static_cast<int32_t>(value));
+    inputs_->SetItem(param, &v, sizeof(int32_t));
+  }
+
+
+  void PostgreSQLStatement::BindInteger64(unsigned int param,
+                                          int64_t value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != INT8OID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    assert(sizeof(int64_t) == 8);
+    int64_t v = htobe64(value);
+    inputs_->SetItem(param, &v, sizeof(int64_t));
+  }
+
+
+  void PostgreSQLStatement::BindString(unsigned int param,
+                                       const std::string& value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != TEXTOID && oids_[param] != BYTEAOID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    if (value.size() == 0)
+    {
+      inputs_->SetItem(param, "", 1 /* end-of-string character */);
+    }
+    else
+    {
+      inputs_->SetItem(param, value.c_str(), 
+                       value.size() + 1);  // "+1" for end-of-string character
+    }
+  }
+
+
+  void PostgreSQLStatement::BindLargeObject(unsigned int param,
+                                            const PostgreSQLLargeObject& value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != OIDOID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    inputs_->SetItem(param, value.GetOid().c_str(), 
+                     value.GetOid().size() + 1);  // "+1" for end-of-string character
+  }
+
+
+  class PostgreSQLStatement::ResultWrapper : public ResultBase
+  {
+  private:
+    std::auto_ptr<PostgreSQLResult>  result_;
+
+  protected:
+    virtual IValue* FetchField(size_t index)
+    {
+      return result_->GetValue(index);
+    }
+
+  public:
+    ResultWrapper(PostgreSQLStatement& statement) :
+      result_(new PostgreSQLResult(statement))
+    {
+      SetFieldsCount(result_->GetColumnsCount());
+      FetchFields();
+    }
+
+    virtual void Next()
+    {
+      result_->Next();
+      FetchFields();
+    }
+
+    virtual bool IsDone() const
+    {
+      return result_->IsDone();
+    }
+  };
+
+
+  IResult* PostgreSQLStatement::Execute(PostgreSQLTransaction& transaction,
+                                        const Dictionary& parameters)
+  {
+    for (size_t i = 0; i < formatter_.GetParametersCount(); i++)
+    {
+      const std::string& name = formatter_.GetParameterName(i);
+      
+      switch (formatter_.GetParameterType(i))
+      {
+        case ValueType_Integer64:
+          BindInteger64(i, dynamic_cast<const Integer64Value&>(parameters.GetValue(name)).GetValue());
+          break;
+
+        case ValueType_Null:
+          BindNull(i);
+          break;
+
+        case ValueType_Utf8String:
+          BindString(i, dynamic_cast<const Utf8StringValue&>
+                     (parameters.GetValue(name)).GetContent());
+          break;
+
+        case ValueType_BinaryString:
+          BindString(i, dynamic_cast<const BinaryStringValue&>
+                     (parameters.GetValue(name)).GetContent());
+          break;
+
+        case ValueType_File:
+        {
+          const FileValue& blob =
+            dynamic_cast<const FileValue&>(parameters.GetValue(name));
+
+          PostgreSQLLargeObject largeObject(database_, blob.GetContent());
+          BindLargeObject(i, largeObject);
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    return new ResultWrapper(*this);
+  }
+
+
+  void PostgreSQLStatement::ExecuteWithoutResult(PostgreSQLTransaction& transaction,
+                                                 const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> dummy(Execute(transaction, parameters));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLStatement.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,118 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include "../Common/IPrecompiledStatement.h"
+#include "../Common/Query.h"
+#include "../Common/GenericFormatter.h"
+
+#include "PostgreSQLDatabase.h"
+#include "PostgreSQLLargeObject.h"
+#include "PostgreSQLTransaction.h"
+
+#include <vector>
+#include <memory>
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancDatabases
+{
+  class PostgreSQLStatement : public IPrecompiledStatement
+  {
+  private:
+    class ResultWrapper;
+    class Inputs;
+    friend class PostgreSQLResult;
+
+    PostgreSQLDatabase& database_;
+    bool readOnly_;
+    std::string id_;
+    std::string sql_;
+    std::vector<unsigned int /*Oid*/>  oids_;
+    std::vector<int>  binary_;
+    boost::shared_ptr<Inputs> inputs_;
+    GenericFormatter formatter_;
+
+    void Prepare();
+
+    void Unprepare();
+
+    void DeclareInputInternal(unsigned int param,
+                              unsigned int /*Oid*/ type);
+
+    void* /* PGresult* */ Execute();
+
+  public:
+    PostgreSQLStatement(PostgreSQLDatabase& database,
+                        const std::string& sql,
+                        bool readOnly);
+
+    PostgreSQLStatement(PostgreSQLDatabase& database,
+                        const Query& query);
+
+    ~PostgreSQLStatement()
+    {
+      Unprepare();
+    }
+    
+    virtual bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    void DeclareInputInteger(unsigned int param);
+    
+    void DeclareInputInteger64(unsigned int param);
+
+    void DeclareInputString(unsigned int param);
+
+    void DeclareInputBinary(unsigned int param);
+
+    void DeclareInputLargeObject(unsigned int param);
+
+    void Run();
+
+    void BindNull(unsigned int param);
+
+    void BindInteger(unsigned int param, int value);
+
+    void BindInteger64(unsigned int param, int64_t value);
+
+    void BindString(unsigned int param, const std::string& value);
+
+    void BindLargeObject(unsigned int param, const PostgreSQLLargeObject& value);
+
+    PostgreSQLDatabase& GetDatabase() const
+    {
+      return database_;
+    }
+
+    IResult* Execute(PostgreSQLTransaction& transaction,
+                     const Dictionary& parameters);
+
+    void ExecuteWithoutResult(PostgreSQLTransaction& transaction,
+                              const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLTransaction.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,124 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLTransaction.h"
+
+#include "PostgreSQLStatement.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  PostgreSQLTransaction::PostgreSQLTransaction(PostgreSQLDatabase& database) :
+    database_(database),
+    isOpen_(false),
+    readOnly_(true)
+  {
+    Begin();
+  }
+
+
+  PostgreSQLTransaction::~PostgreSQLTransaction()
+  {
+    if (isOpen_)
+    {
+      LOG(WARNING) << "PostgreSQL: An active PostgreSQL transaction was dismissed";
+
+      try
+      {
+        database_.Execute("ABORT");
+      }
+      catch (Orthanc::OrthancException&)
+      {
+      }
+    }
+  }
+
+
+  void PostgreSQLTransaction::Begin()
+  {
+    if (isOpen_) 
+    {
+      LOG(ERROR) << "PostgreSQL: Beginning a transaction twice!";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    database_.Execute("BEGIN");
+    database_.Execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+    readOnly_ = true;
+    isOpen_ = true;
+  }
+
+
+  void PostgreSQLTransaction::Rollback() 
+  {
+    if (!isOpen_) 
+    {
+      LOG(ERROR) << "PostgreSQL: Attempting to rollback a nonexistent transaction. "
+                 << "Did you remember to call Begin()?";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    database_.Execute("ABORT");
+    isOpen_ = false;
+  }
+
+
+  void PostgreSQLTransaction::Commit() 
+  {
+    if (!isOpen_) 
+    {
+      LOG(ERROR) << "PostgreSQL: Attempting to roll back a nonexistent transaction. "
+                 << "Did you remember to call Begin()?";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    database_.Execute("COMMIT");
+    isOpen_ = false;
+  }
+
+
+  IResult* PostgreSQLTransaction::Execute(IPrecompiledStatement& statement,
+                                          const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> result(dynamic_cast<PostgreSQLStatement&>(statement).Execute(*this, parameters));
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+
+  return result.release();
+  }
+
+
+  void PostgreSQLTransaction::ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                                   const Dictionary& parameters)
+  {
+    dynamic_cast<PostgreSQLStatement&>(statement).ExecuteWithoutResult(*this, parameters);
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLTransaction.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_POSTGRESQL != 1
+#  error PostgreSQL support must be enabled to use this file
+#endif
+
+#include "../Common/ITransaction.h"
+
+#include "PostgreSQLDatabase.h"
+
+namespace OrthancDatabases
+{
+  class PostgreSQLTransaction : public ITransaction
+  {
+  private:
+    PostgreSQLDatabase& database_;
+    bool isOpen_;
+    bool readOnly_;
+
+  public:
+    explicit PostgreSQLTransaction(PostgreSQLDatabase& database);
+
+    ~PostgreSQLTransaction();
+
+    virtual bool IsReadOnly() const 
+    {
+      return readOnly_;
+    }
+
+    void Begin();
+
+    virtual void Rollback();
+
+    virtual void Commit();
+
+    virtual IResult* Execute(IPrecompiledStatement& statement,
+                             const Dictionary& parameters);
+
+    virtual void ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                      const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteDatabase.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteDatabase.h"
+
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  void SQLiteDatabase::Execute(const std::string& sql)
+  {
+    if (!connection_.Execute(sql))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+  }
+    
+
+  IPrecompiledStatement* SQLiteDatabase::Compile(const Query& query)
+  {
+    return new SQLiteStatement(*this, query);
+  }
+  
+
+  ITransaction* SQLiteDatabase::CreateTransaction()
+  {
+    return new SQLiteTransaction(*this);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteDatabase.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SQLITE != 1
+#  error SQLite support must be enabled to use this file
+#endif
+
+#include "../Common/IDatabase.h"
+
+#include <Core/SQLite/Connection.h>
+
+namespace OrthancDatabases
+{
+  class SQLiteDatabase : public IDatabase
+  {
+  private:
+    Orthanc::SQLite::Connection  connection_;
+    
+  public:
+    void OpenInMemory()
+    {
+      connection_.OpenInMemory();
+    }
+
+    void Open(const std::string& path)
+    {
+      connection_.Open(path);
+    }
+    
+    Orthanc::SQLite::Connection& GetObject()
+    {
+      return connection_;
+    }
+
+    void Execute(const std::string& sql);
+
+    bool DoesTableExist(const std::string& table)
+    {
+      return connection_.DoesTableExist(table.c_str());
+    }
+    
+    int64_t GetLastInsertRowId() const
+    {
+      return connection_.GetLastInsertRowId();
+    }
+    
+    virtual Dialect GetDialect() const
+    {
+      return Dialect_SQLite;
+    }
+
+    virtual IPrecompiledStatement* Compile(const Query& query);
+
+    virtual ITransaction* CreateTransaction();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteResult.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteResult.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/Utf8StringValue.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  SQLiteResult::SQLiteResult(SQLiteStatement& statement) :
+    statement_(statement)
+  {
+    if (statement_.GetObject().ColumnCount() < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      SetFieldsCount(statement_.GetObject().ColumnCount());
+    }
+    
+    done_ = !statement_.GetObject().Step();
+    FetchFields();
+  }
+
+
+  IValue* SQLiteResult::FetchField(size_t index)
+  {
+    switch (statement_.GetObject().GetColumnType(index))
+    {
+      case Orthanc::SQLite::COLUMN_TYPE_INTEGER:
+        return new Integer64Value(statement_.GetObject().ColumnInt64(index));
+        
+      case Orthanc::SQLite::COLUMN_TYPE_TEXT:
+        return new Utf8StringValue(statement_.GetObject().ColumnString(index));
+        
+      case Orthanc::SQLite::COLUMN_TYPE_BLOB:
+        return new BinaryStringValue(statement_.GetObject().ColumnString(index));
+        
+      case Orthanc::SQLite::COLUMN_TYPE_NULL:
+        return new NullValue;
+        
+      case Orthanc::SQLite::COLUMN_TYPE_FLOAT:
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+  
+  
+  void SQLiteResult::Next()
+  {
+    if (done_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      done_ = !statement_.GetObject().Step();
+      FetchFields();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteResult.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SQLITE != 1
+#  error SQLite support must be enabled to use this file
+#endif
+
+#include "SQLiteStatement.h"
+#include "../Common/ResultBase.h"
+
+namespace OrthancDatabases
+{
+  class SQLiteResult : public ResultBase
+  {
+  private:
+    SQLiteStatement&   statement_;
+    bool               done_;
+
+    void StepInternal();
+
+  protected:
+    virtual IValue* FetchField(size_t index);
+    
+  public:
+    SQLiteResult(SQLiteStatement& statement);
+    
+    virtual bool IsDone() const
+    {
+      return done_;
+    }
+
+    virtual void Next();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteStatement.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteStatement.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/FileValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/Query.h"
+#include "../Common/Utf8StringValue.h"
+#include "SQLiteResult.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  SQLiteStatement::SQLiteStatement(SQLiteDatabase& database,
+                                   const Query& query) :
+    readOnly_(query.IsReadOnly()),
+    formatter_(Dialect_SQLite)
+  {
+    std::string sql;
+    query.Format(sql, formatter_);
+    
+    statement_.reset(new Orthanc::SQLite::Statement(database.GetObject(), sql));    
+  }
+
+
+  Orthanc::SQLite::Statement& SQLiteStatement::GetObject()
+  {
+    if (statement_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return *statement_;
+    }
+  }
+
+
+  void SQLiteStatement::BindParameters(const Dictionary& parameters)
+  {
+    statement_->Reset();
+    
+    for (size_t i = 0; i < formatter_.GetParametersCount(); i++)
+    {
+      const std::string& name = formatter_.GetParameterName(i);
+      
+      switch (formatter_.GetParameterType(i))
+      {
+        case ValueType_BinaryString:
+        {
+          const BinaryStringValue& blob =
+            dynamic_cast<const BinaryStringValue&>(parameters.GetValue(name));
+          statement_->BindBlob(i, blob.GetBuffer(), blob.GetSize());
+          break;
+        }
+
+        case ValueType_File:
+        {
+          const FileValue& blob =
+            dynamic_cast<const FileValue&>(parameters.GetValue(name));
+          statement_->BindBlob(i, blob.GetBuffer(), blob.GetSize());
+          break;
+        }
+
+        case ValueType_Integer64:
+          statement_->BindInt64(i, dynamic_cast<const Integer64Value&>
+                                (parameters.GetValue(name)).GetValue());
+          break;
+
+        case ValueType_Null:
+          statement_->BindNull(i);
+          break;
+
+        case ValueType_Utf8String:
+          statement_->BindString(i, dynamic_cast<const Utf8StringValue&>
+                                 (parameters.GetValue(name)).GetContent());
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }    
+  }
+
+  
+  IResult* SQLiteStatement::Execute(SQLiteTransaction& transaction,
+                                    const Dictionary& parameters)
+  {
+    BindParameters(parameters);
+    return new SQLiteResult(*this);
+  }
+
+
+  void SQLiteStatement::ExecuteWithoutResult(SQLiteTransaction& transaction,
+                                             const Dictionary& parameters)
+  {
+    BindParameters(parameters);
+
+    if (!statement_->Run())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteStatement.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SQLITE != 1
+#  error SQLite support must be enabled to use this file
+#endif
+
+#include "SQLiteDatabase.h"
+#include "SQLiteTransaction.h"
+#include "../Common/GenericFormatter.h"
+
+#include <Core/SQLite/Statement.h>
+
+#include <memory>
+
+namespace OrthancDatabases
+{
+  class SQLiteStatement : public IPrecompiledStatement
+  {
+  private:
+    std::auto_ptr<Orthanc::SQLite::Statement>  statement_;
+    bool                                       readOnly_;
+    GenericFormatter                           formatter_;
+
+    void BindParameters(const Dictionary& parameters);
+    
+  public:
+    SQLiteStatement(SQLiteDatabase& database,
+                    const Query& query);
+
+    virtual bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    Orthanc::SQLite::Statement& GetObject();
+
+    IResult* Execute(SQLiteTransaction& transaction,
+                     const Dictionary& parameters);
+
+    void ExecuteWithoutResult(SQLiteTransaction& transaction,
+                              const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteTransaction.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteTransaction.h"
+
+#include "SQLiteResult.h"
+#include "SQLiteStatement.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  SQLiteTransaction::SQLiteTransaction(SQLiteDatabase& database) :
+    transaction_(database.GetObject()),
+    readOnly_(true)
+  {
+    transaction_.Begin();
+
+    if (!transaction_.IsOpen())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  IResult* SQLiteTransaction::Execute(IPrecompiledStatement& statement,
+                                      const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> result(dynamic_cast<SQLiteStatement&>(statement).Execute(*this, parameters));
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+    
+    return result.release();
+  }
+
+  void SQLiteTransaction::ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                               const Dictionary& parameters)
+  {
+    dynamic_cast<SQLiteStatement&>(statement).ExecuteWithoutResult(*this, parameters);
+
+    if (!statement.IsReadOnly())
+    {
+      readOnly_ = false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/SQLite/SQLiteTransaction.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,65 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SQLITE != 1
+#  error SQLite support must be enabled to use this file
+#endif
+
+#include "../Common/ITransaction.h"
+#include "SQLiteDatabase.h"
+
+#include <Core/SQLite/Transaction.h>
+
+namespace OrthancDatabases
+{
+  class SQLiteTransaction : public ITransaction
+  {
+  private:
+    Orthanc::SQLite::Transaction  transaction_;
+    bool                          readOnly_;
+    
+  public:
+    SQLiteTransaction(SQLiteDatabase& database);
+
+    virtual bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    virtual void Rollback()
+    {
+      transaction_.Rollback();
+    }      
+    
+    virtual void Commit()
+    {
+      transaction_.Commit();
+    }
+
+    virtual IResult* Execute(IPrecompiledStatement& statement,
+                             const Dictionary& parameters);
+
+    virtual void ExecuteWithoutResult(IPrecompiledStatement& statement,
+                                      const Dictionary& parameters);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/CMakeLists.txt	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancMySQL)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+  set(ORTHANC_FRAMEWORK_BRANCH "jobs")  # TODO remove this
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.3.2")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginParameters.cmake)
+
+set(ENABLE_MYSQL_BACKEND ON)
+  
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginConfiguration.cmake)
+
+EmbedResources(
+  MYSQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
+  )
+
+add_library(OrthancMySQLIndex SHARED
+  Plugins/MySQLIndex.cpp
+  Plugins/IndexPlugin.cpp
+  ${DATABASES_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+message("Setting the version of the libraries to ${ORTHANC_PLUGIN_VERSION}")
+
+add_definitions(
+  -DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  -DHAS_ORTHANC_EXCEPTION=1
+  )
+
+#set_target_properties(OrthancMySQLStorage PROPERTIES 
+#  VERSION ${ORTHANC_PLUGIN_VERSION} 
+#  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+#  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+#  )
+
+set_target_properties(OrthancMySQLIndex PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  )
+
+install(
+  TARGETS OrthancMySQLIndex  # OrthancMySQLStorage  TODO
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
+
+
+add_executable(UnitTests
+  Plugins/MySQLIndex.cpp
+  UnitTests/UnitTestsMain.cpp
+  ${DATABASES_SOURCES}
+  ${GOOGLE_TEST_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+target_link_libraries(UnitTests ${GOOGLE_TEST_LIBRARIES})
+set_target_properties(UnitTests PROPERTIES
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/NEWS	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,4 @@
+Pending changes in the mainline
+===============================
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/Plugins/IndexPlugin.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,125 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLIndex.h"
+#include "../../Framework/MySQL/MySQLDatabase.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Logging.h>
+
+static OrthancPluginContext* context_ = NULL;
+static std::auto_ptr<OrthancDatabases::MySQLIndex> backend_;
+
+
+
+static bool DisplayPerformanceWarning()
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context_, "Performance warning in MySQL index: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    Orthanc::Logging::Initialize(context);
+
+    context_ = context;
+    assert(DisplayPerformanceWarning());
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context_, "Stores the Orthanc index into a MySQL database.");
+
+    OrthancPlugins::OrthancConfiguration configuration(context);
+
+    if (!configuration.IsSection("MySQL"))
+    {
+      LOG(WARNING) << "No available configuration for the MySQL index plugin";
+      return 0;
+    }
+
+    OrthancPlugins::OrthancConfiguration mysql;
+    configuration.GetSection(mysql, "MySQL");
+
+    bool enable;
+    if (!mysql.LookupBooleanValue(enable, "EnableIndex") ||
+        !enable)
+    {
+      LOG(WARNING) << "The MySQL index is currently disabled, set \"EnableIndex\" "
+                   << "to \"true\" in the \"MySQL\" section of the configuration file of Orthanc";
+      return 0;
+    }
+
+    try
+    {
+      OrthancDatabases::MySQLParameters parameters(mysql);
+
+      /* Create the database back-end */
+      backend_.reset(new OrthancDatabases::MySQLIndex(parameters));
+
+      /* Register the MySQL index into Orthanc */
+      OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_);
+    }
+    catch (std::runtime_error& e)
+    {
+      OrthancPluginLogError(context_, e.what());
+      return -1;
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "MySQL index is finalizing");
+    backend_.reset(NULL);
+
+    OrthancDatabases::MySQLDatabase::GlobalFinalization();
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "mysql-index";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/Plugins/MySQLIndex.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,272 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MySQLIndex.h"
+
+#include "../../Framework/Plugins/GlobalProperties.h"
+#include "../../Framework/MySQL/MySQLDatabase.h"
+#include "../../Framework/MySQL/MySQLTransaction.h"
+
+#include <EmbeddedResources.h>  // Auto-generated file
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <ctype.h>
+
+namespace OrthancDatabases
+{
+  IDatabase* MySQLIndex::OpenInternal()
+  {
+    uint32_t expectedVersion = 6;
+    if (context_)
+    {
+      expectedVersion = OrthancPluginGetExpectedDatabaseVersion(context_);
+    }
+    else
+    {
+      // This case only occurs during unit testing
+      expectedVersion = 6;
+    }
+
+    // Check the expected version of the database
+    if (expectedVersion != 6)
+    {
+      LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc "
+                 << "expecting the DB schema version " << expectedVersion 
+                 << ", but this plugin is only compatible with version 6";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+    if (parameters_.GetDatabase().empty())
+    {
+      LOG(ERROR) << "Empty database name";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    for (size_t i = 0; i < parameters_.GetDatabase().length(); i++)
+    {
+      if (!isalnum(parameters_.GetDatabase() [i]))
+      {
+        LOG(ERROR) << "Only alphanumeric characters are allowed in a "
+                   << "MySQL database name: \"" << parameters_.GetDatabase() << "\"";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);          
+      }
+    }
+    
+    if (clearAll_)
+    {
+      MySQLParameters p = parameters_;
+      const std::string database = p.GetDatabase();
+      p.SetDatabase("");
+
+      MySQLDatabase db(p);
+      db.Open();
+
+      MySQLTransaction t(db);
+      db.Execute("DROP DATABASE IF EXISTS " + database);
+      db.Execute("CREATE DATABASE " + database);
+      t.Commit();
+    }
+    
+    std::auto_ptr<MySQLDatabase> db(new MySQLDatabase(parameters_));
+
+    db->Open();
+    db->Execute("ALTER DATABASE " + parameters_.GetDatabase() + 
+                " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
+    db->Execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+
+    {
+      MySQLTransaction t(*db);
+
+      if (!db->DoesTableExist(t, "Resources"))
+      {
+        std::string query;
+
+        Orthanc::EmbeddedResources::GetFileResource
+          (query, Orthanc::EmbeddedResources::MYSQL_PREPARE_INDEX);
+        db->Execute(query);
+
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion);
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, 1);
+      }
+
+      t.Commit();
+    }
+
+    {
+      MySQLTransaction t(*db);
+
+      if (!db->DoesTableExist(t, "Resources"))
+      {
+        LOG(ERROR) << "Corrupted MySQL database";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);        
+      }
+
+      int version = 0;
+      if (!LookupGlobalIntegerProperty(version, *db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion) ||
+          version != 6)
+      {
+        LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema version: " << version;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+
+      int revision;
+      if (!LookupGlobalIntegerProperty(revision, *db, t, Orthanc::GlobalProperty_DatabasePatchLevel))
+      {
+        revision = 1;
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
+      }
+
+      if (revision != 1)
+      {
+        LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema revision: " << revision;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+
+      t.Rollback();
+    }
+          
+    return db.release();
+  }
+
+
+  MySQLIndex::MySQLIndex(const MySQLParameters& parameters) :
+    IndexBackend(new Factory(*this)),
+    context_(NULL),
+    parameters_(parameters),
+    clearAll_(false)
+  {
+  }
+
+
+  int64_t MySQLIndex::CreateResource(const char* publicId,
+                                     OrthancPluginResourceType type)
+  {
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "INSERT INTO Resources VALUES(${}, ${type}, ${id}, NULL)");
+    
+      statement.SetParameterType("id", ValueType_Utf8String);
+      statement.SetParameterType("type", ValueType_Integer64);
+
+      Dictionary args;
+      args.SetUtf8Value("id", publicId);
+      args.SetIntegerValue("type", static_cast<int>(type));
+    
+      statement.Execute(args);
+    }
+
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "SELECT LAST_INSERT_ID()");
+
+      statement.Execute();
+      
+      return ReadInteger64(statement, 0);
+    }
+  }
+
+
+  void MySQLIndex::DeleteResource(int64_t id)
+  {
+    ClearDeletedFiles();
+
+    // Recursive exploration of resources to be deleted, from the "id"
+    // resource to the top of the tree of resources
+    
+    bool done = false;
+
+    while (!done)
+    {
+      int64_t parentId;
+      
+      {
+        DatabaseManager::CachedStatement lookupSiblings(
+          STATEMENT_FROM_HERE, GetManager(),
+          "SELECT parentId FROM Resources "
+          "WHERE parentId = (SELECT parentId FROM Resources WHERE internalId=${id});");
+
+        lookupSiblings.SetParameterType("id", ValueType_Integer64);
+
+        Dictionary args;
+        args.SetIntegerValue("id", id);
+    
+        lookupSiblings.Execute(args);
+
+        if (lookupSiblings.IsDone())
+        {
+          // "id" is a root node
+          done = true;
+        }
+        else
+        {
+          parentId = ReadInteger64(lookupSiblings, 0);
+          lookupSiblings.Next();
+
+          if (lookupSiblings.IsDone())
+          {
+            // "id" has no sibling node, recursively remove
+            done = false;
+            id = parentId;
+          }
+          else
+          {
+            // "id" has at least one sibling node: the parent node is the remaining ancestor
+            done = true;
+
+            DatabaseManager::CachedStatement parent(
+              STATEMENT_FROM_HERE, GetManager(),
+              "SELECT publicId, resourceType FROM Resources WHERE internalId=${id};");
+            
+            parent.SetParameterType("id", ValueType_Integer64);
+
+            Dictionary args;
+            args.SetIntegerValue("id", parentId);
+    
+            parent.Execute(args);
+
+            GetOutput().SignalRemainingAncestor(
+              ReadString(parent, 0),
+              static_cast<OrthancPluginResourceType>(ReadInteger32(parent, 1)));
+          }
+        }
+      }
+    }
+
+    {
+      DatabaseManager::CachedStatement deleteHierarchy(
+        STATEMENT_FROM_HERE, GetManager(),
+        "DELETE FROM Resources WHERE internalId IN (SELECT * FROM (SELECT internalId FROM Resources WHERE internalId=${id} OR parentId=${id} OR parentId IN (SELECT internalId FROM Resources WHERE parentId=${id}) OR parentId IN (SELECT internalId FROM Resources WHERE parentId IN (SELECT internalId FROM Resources WHERE parentId=${id}))) as t);");
+      
+      deleteHierarchy.SetParameterType("id", ValueType_Integer64);
+      
+      Dictionary args;
+      args.SetIntegerValue("id", id);
+    
+      deleteHierarchy.Execute(args);
+    }
+
+    SignalDeletedFiles();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/Plugins/MySQLIndex.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Plugins/IndexBackend.h"
+#include "../../Framework/MySQL/MySQLParameters.h"
+
+namespace OrthancDatabases
+{
+  class MySQLIndex : public IndexBackend 
+  {
+  private:
+    class Factory : public IDatabaseFactory
+    {
+    private:
+      MySQLIndex&  that_;
+
+    public:
+      Factory(MySQLIndex& that) :
+      that_(that)
+      {
+      }
+
+      virtual Dialect GetDialect() const
+      {
+        return Dialect_MySQL;
+      }
+
+      virtual IDatabase* Open()
+      {
+        return that_.OpenInternal();
+      }
+    };
+
+    OrthancPluginContext*  context_;
+    MySQLParameters        parameters_;
+    bool                   clearAll_;
+
+    IDatabase* OpenInternal();
+
+  public:
+    MySQLIndex(const MySQLParameters& parameters);
+
+    void SetOrthancPluginContext(OrthancPluginContext* context)
+    {
+      context_ = context;
+    }
+
+    void SetClearAll(bool clear)
+    {
+      clearAll_ = clear;
+    }
+
+    virtual int64_t CreateResource(const char* publicId,
+                                   OrthancPluginResourceType type);
+
+    virtual void DeleteResource(int64_t id);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/Plugins/PrepareIndex.sql	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,143 @@
+CREATE TABLE GlobalProperties(
+       property INTEGER PRIMARY KEY,
+       value TEXT
+       );
+
+-- Set GlobalProperty_DatabaseSchemaVersion
+INSERT INTO GlobalProperties VALUES (1, '6');
+       
+CREATE TABLE Resources(
+       internalId BIGINT NOT NULL AUTO_INCREMENT,
+       resourceType INTEGER NOT NULL,
+       publicId VARCHAR(64) NOT NULL,
+       parentId BIGINT,
+       PRIMARY KEY(internalId)
+       -- MySQL does not allow recursive foreign keys on the same table
+       -- CONSTRAINT Resources1 FOREIGN KEY (parentId) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE MainDicomTags(
+       id BIGINT NOT NULL,
+       tagGroup INTEGER NOT NULL,
+       tagElement INTEGER NOT NULL,
+       value VARCHAR(255),
+       PRIMARY KEY(id, tagGroup, tagElement),
+       CONSTRAINT MainDicomTags1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE DicomIdentifiers(
+       id BIGINT NOT NULL,
+       tagGroup INTEGER NOT NULL,
+       tagElement INTEGER NOT NULL,
+       value VARCHAR(255),
+       PRIMARY KEY(id, tagGroup, tagElement),
+       CONSTRAINT DicomIdentifiers1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE Metadata(
+       id BIGINT NOT NULL,
+       type INTEGER NOT NULL,
+       value TEXT,
+       PRIMARY KEY(id, type),
+       CONSTRAINT Metadata1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE AttachedFiles(
+       id BIGINT NOT NULL,
+       fileType INTEGER,
+       uuid VARCHAR(64) NOT NULL,
+       compressedSize BIGINT,
+       uncompressedSize BIGINT,
+       compressionType INTEGER,
+       uncompressedHash VARCHAR(40),
+       compressedHash VARCHAR(40),
+       PRIMARY KEY(id, fileType),
+       CONSTRAINT AttachedFiles1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );              
+
+CREATE TABLE Changes(
+       seq BIGINT NOT NULL AUTO_INCREMENT,
+       changeType INTEGER,
+       internalId BIGINT NOT NULL,
+       resourceType INTEGER,
+       date VARCHAR(64),
+       PRIMARY KEY(seq),
+       CONSTRAINT Changes1 FOREIGN KEY (internalId) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE ExportedResources(
+       seq BIGINT NOT NULL AUTO_INCREMENT,
+       resourceType INTEGER,
+       publicId VARCHAR(64),
+       remoteModality TEXT,
+       patientId VARCHAR(64),
+       studyInstanceUid TEXT,
+       seriesInstanceUid TEXT,
+       sopInstanceUid TEXT,
+       date VARCHAR(64),
+       PRIMARY KEY(seq)
+       ); 
+
+CREATE TABLE PatientRecyclingOrder(
+       seq BIGINT NOT NULL AUTO_INCREMENT,
+       patientId BIGINT NOT NULL,
+       PRIMARY KEY(seq),
+       CONSTRAINT PatientRecyclingOrder1 FOREIGN KEY (patientId) REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE INDEX ChildrenIndex ON Resources(parentId);
+CREATE INDEX PublicIndex ON Resources(publicId);
+CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
+
+CREATE INDEX MainDicomTagsIndex ON MainDicomTags(id);
+CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id);
+CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement);
+CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value);
+
+CREATE INDEX ChangesIndex ON Changes(internalId);
+
+
+-- New tables wrt. Orthanc core
+CREATE TABLE DeletedFiles(
+       uuid VARCHAR(64) NOT NULL,      -- 0
+       fileType INTEGER,               -- 1
+       compressedSize BIGINT,          -- 2
+       uncompressedSize BIGINT,        -- 3
+       compressionType INTEGER,        -- 4
+       uncompressedHash VARCHAR(40),   -- 5
+       compressedHash VARCHAR(40)      -- 6
+       );
+-- End of differences
+
+
+
+-- NB: Character "@" is used to replace the semicolon characters in triggers
+
+-- In MySQL, this trigger is only used if replacing some attachment
+CREATE TRIGGER AttachedFileDeleted
+AFTER DELETE ON AttachedFiles
+FOR EACH ROW
+BEGIN
+  INSERT INTO DeletedFiles VALUES(old.uuid, old.filetype, old.compressedSize,
+                                  old.uncompressedSize, old.compressionType,
+                                  old.uncompressedHash, old.compressedHash)@
+END;
+
+
+CREATE TRIGGER ResourceDeleted
+BEFORE DELETE ON Resources   -- WARNING: Must be "BEFORE", otherwise the attached file is already deleted
+FOR EACH ROW
+BEGIN
+   INSERT INTO DeletedFiles SELECT uuid, fileType, compressedSize, uncompressedSize, compressionType, uncompressedHash, compressedHash FROM AttachedFiles WHERE id=old.internalId@
+END;
+
+
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW
+BEGIN
+  IF new.resourceType = 0 THEN  -- The "0" corresponds to "OrthancPluginResourceType_Patient"
+    INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId)@
+  END IF@
+END;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQL/UnitTests/UnitTestsMain.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,87 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Plugins/MySQLIndex.h"
+
+OrthancDatabases::MySQLParameters globalParameters_;
+
+#include "../../Framework/Plugins/IndexUnitTests.h"
+#include "../../Framework/MySQL/MySQLDatabase.h"
+
+#include <Core/Logging.h>
+
+#include <gtest/gtest.h>
+
+
+TEST(MySQLIndex, Lock)
+{
+  OrthancDatabases::MySQLParameters noLock = globalParameters_;
+  noLock.SetLock(false);
+
+  OrthancDatabases::MySQLParameters lock = globalParameters_;
+  lock.SetLock(true);
+
+  OrthancDatabases::MySQLIndex db1(noLock);
+  db1.SetClearAll(true);
+  db1.Open();
+
+  {
+    OrthancDatabases::MySQLIndex db2(lock);
+    db2.Open();
+
+    OrthancDatabases::MySQLIndex db3(lock);
+    ASSERT_THROW(db3.Open(), Orthanc::OrthancException);
+  }
+
+  OrthancDatabases::MySQLIndex db4(lock);
+  db4.Open();
+}
+
+
+int main(int argc, char **argv)
+{
+  if (argc < 5)
+  {
+    std::cerr << "Usage: " << argv[0] << " <socket> <username> <password> <database>"
+              << std::endl << std::endl
+              << "Example: " << argv[0] << " /var/run/mysqld/mysqld.sock root root orthanctest"
+              << std::endl << std::endl;
+    return -1;
+  }
+
+  globalParameters_.SetUnixSocket(argv[1]);
+  globalParameters_.SetUsername(argv[2]);
+  globalParameters_.SetPassword(argv[3]);
+  globalParameters_.SetDatabase(argv[4]);
+
+  ::testing::InitGoogleTest(&argc, argv);
+  Orthanc::Logging::Initialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+  Orthanc::Logging::EnableTraceLevel(true);
+
+  int result = RUN_ALL_TESTS();
+
+  Orthanc::Logging::Finalize();
+
+  OrthancDatabases::MySQLDatabase::GlobalFinalization();
+
+  return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/CMakeLists.txt	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,70 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancPostgreSQL)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+  set(ORTHANC_FRAMEWORK_BRANCH "jobs")  # TODO remove this
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.3.2")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginParameters.cmake)
+
+set(ENABLE_POSTGRESQL_BACKEND ON)
+  
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginConfiguration.cmake)
+
+EmbedResources(
+  POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
+  )
+
+add_library(OrthancPostgreSQLIndex SHARED
+  Plugins/PostgreSQLIndex.cpp
+  Plugins/IndexPlugin.cpp
+  ${DATABASES_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+message("Setting the version of the libraries to ${ORTHANC_PLUGIN_VERSION}")
+
+add_definitions(
+  -DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  -DHAS_ORTHANC_EXCEPTION=1
+  )
+
+#set_target_properties(OrthancPostgreSQLStorage PROPERTIES 
+#  VERSION ${ORTHANC_PLUGIN_VERSION} 
+#  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+#  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+#  )
+
+set_target_properties(OrthancPostgreSQLIndex PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  )
+
+install(
+  TARGETS OrthancPostgreSQLIndex  # OrthancPostgreSQLStorage  TODO
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
+
+
+add_executable(UnitTests
+  Plugins/PostgreSQLIndex.cpp
+  UnitTests/UnitTestsMain.cpp
+  UnitTests/PostgreSQLTests.cpp
+  ${DATABASES_SOURCES}
+  ${GOOGLE_TEST_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+target_link_libraries(UnitTests ${GOOGLE_TEST_LIBRARIES})
+set_target_properties(UnitTests PROPERTIES
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/NEWS	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,78 @@
+Pending changes in the mainline
+===============================
+
+* Migration into the "orthanc-databases" repository
+* Full refactoring to share code with MySQL
+* Fix issue 41 (Additional range IdentifierConstraintType in Orthanc)
+  => This fix requires SDK version: 1.4.0 <=
+* Fix issue 47 (Index improvements for PG plugin)
+* Fix issue 76 (PG connection shall reconnect/retry in case it loses
+  the connection to the server)
+
+
+Release 2.1 (2018-04-20)
+========================
+
+* Running transactions in "Serializable" isolation level to avoid 
+  inconsistencies if multiple Orthanc are writing to the same DB
+* Upgrade to PostgreSQL 9.6.1 client library for static builds
+* Performance warning if runtime debug assertions are turned on
+* Fix issue 62 (use correct type for lo_read() value)
+* Fix issue 63 (allow to connect without specifing username and/or port)
+* Fix issue 68 (PostgreSQL plugin needs extra flags to compile)
+* Resort to Orthanc framework
+* Support of Linux Standard Base, OpenBSD and FreeBSD
+
+
+Release 2.0 (2015-12-02)
+========================
+
+=> Minimum SDK version: 0.9.5 <=
+=> Supported database versions: 5 (upgrade only) and 6 <=
+
+* Support version 6 of the database schema
+* The "value" column of tables "MainDicomTags" and "DicomIdentifiers" are now TEXT instead of BYTEA
+
+
+Release 1.3 (2015-10-07)
+========================
+
+=> Minimum SDK version: 0.9.4 <=
+=> Supported database versions: 5 <=
+
+* Fix build with Orthanc plugin SDK 0.9.4
+* Implementation of "GetAllPublicIdsWithLimit" extension
+* Implementation of "UpgradeDatabase" extension
+
+
+Release 1.2 (2015-08-02)
+========================
+
+=> Minimum SDK version: 0.9.1 <=
+
+* Inject version information into Windows binaries
+* CMake flag to prevent compiling the unit tests (if no PostgreSQL test server is available)
+* Update to Boost 1.58.0 for static and Windows builds
+* Support of OS X compilation
+
+
+Release 1.1 (2015-07-03)
+========================
+
+* Fixes
+* Support of Visual Studio 2008
+* Support of FreeBSD thanks Mikhail <mp39590@gmail.com>
+
+
+Release 1.0 (2015-02-27)
+========================
+
+* Use of advisory locks
+* Support of connection URI in PostgreSQL
+* Options "EnableIndex" and "EnableStorage" to explicitly enable PostgreSQL
+
+
+2015-02-06
+==========
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/IndexPlugin.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLIndex.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Logging.h>
+
+static OrthancPluginContext* context_ = NULL;
+static std::auto_ptr<OrthancDatabases::PostgreSQLIndex> backend_;
+
+
+
+static bool DisplayPerformanceWarning()
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context_, "Performance warning in PostgreSQL index: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    Orthanc::Logging::Initialize(context);
+
+    context_ = context;
+    assert(DisplayPerformanceWarning());
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context_, "Stores the Orthanc index into a PostgreSQL database.");
+
+    OrthancPlugins::OrthancConfiguration configuration(context);
+
+    if (!configuration.IsSection("PostgreSQL"))
+    {
+      LOG(WARNING) << "No available configuration for the PostgreSQL index plugin";
+      return 0;
+    }
+
+    OrthancPlugins::OrthancConfiguration postgresql;
+    configuration.GetSection(postgresql, "PostgreSQL");
+
+    bool enable;
+    if (!postgresql.LookupBooleanValue(enable, "EnableIndex") ||
+        !enable)
+    {
+      LOG(WARNING) << "The PostgreSQL index is currently disabled, set \"EnableIndex\" "
+                   << "to \"true\" in the \"PostgreSQL\" section of the configuration file of Orthanc";
+      return 0;
+    }
+
+    try
+    {
+      OrthancDatabases::PostgreSQLParameters parameters(postgresql);
+
+      /* Create the database back-end */
+      backend_.reset(new OrthancDatabases::PostgreSQLIndex(parameters));
+
+      /* Register the PostgreSQL index into Orthanc */
+      OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_);
+    }
+    catch (std::runtime_error& e)
+    {
+      OrthancPluginLogError(context_, e.what());
+      return -1;
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "PostgreSQL index is finalizing");
+    backend_.reset(NULL);
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "postgresql-index";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,181 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLIndex.h"
+
+#include "../../Framework/Plugins/GlobalProperties.h"
+#include "../../Framework/PostgreSQL/PostgreSQLDatabase.h"
+#include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
+
+#include <EmbeddedResources.h>  // Auto-generated file
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+
+namespace Orthanc
+{
+  // Some aliases for internal properties
+  static const GlobalProperty GlobalProperty_HasTrigramIndex = GlobalProperty_DatabaseInternal0;
+}
+
+
+namespace OrthancDatabases
+{
+  IDatabase* PostgreSQLIndex::OpenInternal()
+  {
+    uint32_t expectedVersion = 6;
+    if (context_)
+    {
+      expectedVersion = OrthancPluginGetExpectedDatabaseVersion(context_);
+    }
+    else
+    {
+      // This case only occurs during unit testing
+      expectedVersion = 6;
+    }
+
+    // Check the expected version of the database
+    if (expectedVersion != 6)
+    {
+      LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc "
+                 << "expecting the DB schema version " << expectedVersion 
+                 << ", but this plugin is only compatible with version 6";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+    std::auto_ptr<PostgreSQLDatabase> db(new PostgreSQLDatabase(parameters_));
+
+    db->Open();
+
+    if (clearAll_)
+    {
+      db->ClearAll();
+    }
+
+    {
+      PostgreSQLTransaction t(*db);
+
+      if (!db->DoesTableExist("Resources"))
+      {
+        std::string query;
+
+        Orthanc::EmbeddedResources::GetFileResource
+          (query, Orthanc::EmbeddedResources::POSTGRESQL_PREPARE_INDEX);
+        db->Execute(query);
+
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion);
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, 1);
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_HasTrigramIndex, 0);
+      }
+          
+      t.Commit();
+    }
+
+    {
+      PostgreSQLTransaction t(*db);
+
+      if (!db->DoesTableExist("Resources"))
+      {
+        LOG(ERROR) << "Corrupted PostgreSQL database";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);        
+      }
+
+      int version = 0;
+      if (!LookupGlobalIntegerProperty(version, *db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion) ||
+          version != 6)
+      {
+        LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema version: " << version;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+
+      int revision;
+      if (!LookupGlobalIntegerProperty(revision, *db, t, Orthanc::GlobalProperty_DatabasePatchLevel))
+      {
+        revision = 1;
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
+      }
+
+      int hasTrigram = 0;
+      if (!LookupGlobalIntegerProperty(hasTrigram, *db, t, Orthanc::GlobalProperty_HasTrigramIndex) ||
+          hasTrigram != 1)
+      {
+        // Apply fix for performance issue (speed up wildcard search by using GIN trigrams)
+        // https://www.postgresql.org/docs/current/static/pgtrgm.html
+        try
+        {
+          LOG(INFO) << "Trying to enable trigram matching on the PostgreSQL database to speed up wildcard searches";
+          db->Execute(
+            "CREATE EXTENSION pg_trgm; "
+            "CREATE INDEX DicomIdentifiersIndexValues_new ON DicomIdentifiers USING gin(value gin_trgm_ops); "
+            "DROP INDEX DicomIdentifiersIndexValues; "
+            "ALTER INDEX DicomIdentifiersIndexValues_new RENAME TO DicomIdentifiersIndexValues;");
+
+          SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_HasTrigramIndex, 1);
+        }
+        catch (Orthanc::OrthancException&)
+        {
+          LOG(WARNING) << "Performance warning: Your PostgreSQL server does not support trigram matching";
+          LOG(WARNING) << "-> Consider installing the \"pg_trgm\" extension on the PostgreSQL server, e.g. on Debian: sudo apt install postgresql-contrib";
+        }
+      }
+
+      if (revision != 1)
+      {
+        LOG(ERROR) << "PostgreSQL plugin is incompatible with database schema revision: " << revision;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+
+      t.Commit();
+    }
+
+    return db.release();
+  }
+
+
+  PostgreSQLIndex::PostgreSQLIndex(const PostgreSQLParameters& parameters) :
+    IndexBackend(new Factory(*this)),
+    context_(NULL),
+    parameters_(parameters),
+    clearAll_(false)
+  {
+  }
+
+  
+  int64_t PostgreSQLIndex::CreateResource(const char* publicId,
+                                          OrthancPluginResourceType type)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "INSERT INTO Resources VALUES(${}, ${type}, ${id}, NULL) RETURNING internalId");
+     
+    statement.SetParameterType("id", ValueType_Utf8String);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetUtf8Value("id", publicId);
+    args.SetIntegerValue("type", static_cast<int>(type));
+     
+    statement.Execute(args);
+
+    return ReadInteger64(statement, 0);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Plugins/IndexBackend.h"
+#include "../../Framework/PostgreSQL/PostgreSQLParameters.h"
+
+namespace OrthancDatabases
+{
+  class PostgreSQLIndex : public IndexBackend 
+  {
+  private:
+    class Factory : public IDatabaseFactory
+    {
+    private:
+      PostgreSQLIndex&  that_;
+
+    public:
+      Factory(PostgreSQLIndex& that) :
+      that_(that)
+      {
+      }
+
+      virtual Dialect GetDialect() const
+      {
+        return Dialect_PostgreSQL;
+      }
+
+      virtual IDatabase* Open()
+      {
+        return that_.OpenInternal();
+      }
+    };
+
+    OrthancPluginContext*  context_;
+    PostgreSQLParameters   parameters_;
+    bool                   clearAll_;
+
+    IDatabase* OpenInternal();
+
+  public:
+    PostgreSQLIndex(const PostgreSQLParameters& parameters);
+
+    void SetOrthancPluginContext(OrthancPluginContext* context)
+    {
+      context_ = context;
+    }
+
+    void SetClearAll(bool clear)
+    {
+      clearAll_ = clear;
+    }
+
+    virtual int64_t CreateResource(const char* publicId,
+                                   OrthancPluginResourceType type);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/PrepareIndex.sql	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,168 @@
+CREATE TABLE GlobalProperties(
+       property INTEGER PRIMARY KEY,
+       value TEXT
+       );
+
+CREATE TABLE Resources(
+       internalId BIGSERIAL NOT NULL PRIMARY KEY,
+       resourceType INTEGER NOT NULL,
+       publicId VARCHAR(64) NOT NULL,
+       parentId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE MainDicomTags(
+       id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, tagGroup, tagElement)
+       );
+
+CREATE TABLE DicomIdentifiers(
+       id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, tagGroup, tagElement)
+       );
+
+CREATE TABLE Metadata(
+       id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,
+       type INTEGER NOT NULL,
+       value TEXT,
+       PRIMARY KEY(id, type)
+       );
+
+CREATE TABLE AttachedFiles(
+       id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,
+       fileType INTEGER,
+       uuid VARCHAR(64) NOT NULL,
+       compressedSize BIGINT,
+       uncompressedSize BIGINT,
+       compressionType INTEGER,
+       uncompressedHash VARCHAR(40),
+       compressedHash VARCHAR(40),
+       PRIMARY KEY(id, fileType)
+       );              
+
+CREATE TABLE Changes(
+       seq BIGSERIAL NOT NULL PRIMARY KEY,
+       changeType INTEGER,
+       internalId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,
+       resourceType INTEGER,
+       date VARCHAR(64)
+       );
+
+CREATE TABLE ExportedResources(
+       seq BIGSERIAL NOT NULL PRIMARY KEY,
+       resourceType INTEGER,
+       publicId VARCHAR(64),
+       remoteModality TEXT,
+       patientId VARCHAR(64),
+       studyInstanceUid TEXT,
+       seriesInstanceUid TEXT,
+       sopInstanceUid TEXT,
+       date VARCHAR(64)
+       ); 
+
+CREATE TABLE PatientRecyclingOrder(
+       seq BIGSERIAL NOT NULL PRIMARY KEY,
+       patientId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE INDEX ChildrenIndex ON Resources(parentId);
+CREATE INDEX PublicIndex ON Resources(publicId);
+CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
+
+CREATE INDEX MainDicomTagsIndex ON MainDicomTags(id);
+CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id);
+CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement);
+CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value);
+
+CREATE INDEX ChangesIndex ON Changes(internalId);
+
+
+-- New tables wrt. Orthanc core
+CREATE TABLE DeletedFiles(
+       uuid VARCHAR(64) NOT NULL,      -- 0
+       fileType INTEGER,               -- 1
+       compressedSize BIGINT,          -- 2
+       uncompressedSize BIGINT,        -- 3
+       compressionType INTEGER,        -- 4
+       uncompressedHash VARCHAR(40),   -- 5
+       compressedHash VARCHAR(40)      -- 6
+       );
+
+CREATE TABLE RemainingAncestor(
+       resourceType INTEGER NOT NULL,
+       publicId VARCHAR(64) NOT NULL
+       );
+
+CREATE TABLE DeletedResources(
+       resourceType INTEGER NOT NULL,
+       publicId VARCHAR(64) NOT NULL
+       );
+-- End of differences
+
+
+CREATE FUNCTION AttachedFileDeletedFunc() 
+RETURNS TRIGGER AS $body$
+BEGIN
+  INSERT INTO DeletedFiles VALUES
+    (old.uuid, old.filetype, old.compressedSize,
+     old.uncompressedSize, old.compressionType,
+     old.uncompressedHash, old.compressedHash);
+  RETURN NULL;
+END;
+$body$ LANGUAGE plpgsql;
+
+CREATE TRIGGER AttachedFileDeleted
+AFTER DELETE ON AttachedFiles
+FOR EACH ROW
+EXECUTE PROCEDURE AttachedFileDeletedFunc();
+
+
+-- The following trigger combines 2 triggers from SQLite:
+-- ResourceDeleted + ResourceDeletedParentCleaning
+CREATE FUNCTION ResourceDeletedFunc() 
+RETURNS TRIGGER AS $body$
+BEGIN
+  --RAISE NOTICE 'Delete resource %', old.parentId;
+  INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId);
+  
+  -- http://stackoverflow.com/a/11299968/881731
+  IF EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId) THEN
+    -- Signal that the deleted resource has a remaining parent
+    INSERT INTO RemainingAncestor
+      SELECT resourceType, publicId FROM Resources WHERE internalId = old.parentId;
+  ELSE
+    -- Delete a parent resource when its unique child is deleted 
+    DELETE FROM Resources WHERE internalId = old.parentId;
+  END IF;
+  RETURN NULL;
+END;
+$body$ LANGUAGE plpgsql;
+
+CREATE TRIGGER ResourceDeleted
+AFTER DELETE ON Resources
+FOR EACH ROW
+EXECUTE PROCEDURE ResourceDeletedFunc();
+
+
+
+CREATE FUNCTION PatientAddedFunc() 
+RETURNS TRIGGER AS $body$
+BEGIN
+  -- The "0" corresponds to "OrthancPluginResourceType_Patient"
+  IF new.resourceType = 0 THEN
+    INSERT INTO PatientRecyclingOrder VALUES (DEFAULT, new.internalId);
+  END IF;
+  RETURN NULL;
+END;
+$body$ LANGUAGE plpgsql;
+
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW
+EXECUTE PROCEDURE PatientAddedFunc();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,349 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <gtest/gtest.h>
+
+#if defined(_WIN32)
+// Fix redefinition of symbols on MinGW (these symbols are manually
+// defined both by PostgreSQL and Google Test)
+#  undef S_IRGRP
+#  undef S_IROTH
+#  undef S_IRWXG
+#  undef S_IRWXO
+#  undef S_IWGRP
+#  undef S_IWOTH
+#  undef S_IXGRP
+#  undef S_IXOTH
+#endif
+
+#include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
+#include "../../Framework/PostgreSQL/PostgreSQLResult.h"
+#include "../../Framework/PostgreSQL/PostgreSQLLargeObject.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace OrthancDatabases;
+
+extern const OrthancDatabases::PostgreSQLParameters  globalParameters_;
+
+
+static OrthancDatabases::PostgreSQLDatabase* CreateTestDatabase(bool clearAll)
+{
+  std::auto_ptr<OrthancDatabases::PostgreSQLDatabase> pg
+    (new OrthancDatabases::PostgreSQLDatabase(globalParameters_));
+
+  pg->Open();
+
+  if (clearAll)
+  {
+    pg->ClearAll();
+  }
+
+  return pg.release();
+}
+
+
+static int64_t CountLargeObjects(PostgreSQLDatabase& db)
+{
+  // Count the number of large objects in the DB
+  PostgreSQLTransaction t(db);
+  PostgreSQLStatement s(db, "SELECT COUNT(*) FROM pg_catalog.pg_largeobject", true);
+  PostgreSQLResult r(s);
+  return r.GetInteger64(0);
+}
+
+
+TEST(PostgreSQL, Basic)
+{
+  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
+
+  ASSERT_FALSE(pg->DoesTableExist("Test"));
+  pg->Execute("CREATE TABLE Test(name INTEGER, value BIGINT)");
+  ASSERT_TRUE(pg->DoesTableExist("Test"));
+
+  PostgreSQLStatement s(*pg, "INSERT INTO Test VALUES ($1,$2)", false);
+  s.DeclareInputInteger(0);
+  s.DeclareInputInteger64(1);
+
+  s.BindInteger(0, 43);
+  s.BindNull(0);
+  s.BindInteger(0, 42);
+  s.BindInteger64(1, -4242);
+  s.Run();
+
+  s.BindInteger(0, 43);
+  s.BindNull(1);
+  s.Run();
+
+  s.BindNull(0);
+  s.BindInteger64(1, 4444);
+  s.Run();
+
+  {
+    PostgreSQLStatement t(*pg, "SELECT name, value FROM Test ORDER BY name", true);
+    PostgreSQLResult r(t);
+
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_FALSE(r.IsNull(0)); ASSERT_EQ(42, r.GetInteger(0));
+    ASSERT_FALSE(r.IsNull(1)); ASSERT_EQ(-4242, r.GetInteger64(1));
+
+    r.Next();
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_FALSE(r.IsNull(0)); ASSERT_EQ(43, r.GetInteger(0));
+    ASSERT_TRUE(r.IsNull(1));
+
+    r.Next();
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_TRUE(r.IsNull(0));
+    ASSERT_FALSE(r.IsNull(1)); ASSERT_EQ(4444, r.GetInteger64(1));
+
+    r.Next();
+    ASSERT_TRUE(r.IsDone());
+  }
+
+  {
+    PostgreSQLStatement t(*pg, "SELECT name, value FROM Test WHERE name=$1", true);
+    t.DeclareInputInteger(0);
+
+    {
+      t.BindInteger(0, 42);
+      PostgreSQLResult r(t);
+      ASSERT_FALSE(r.IsDone());
+      ASSERT_FALSE(r.IsNull(0)); ASSERT_EQ(42, r.GetInteger(0));
+      ASSERT_FALSE(r.IsNull(1)); ASSERT_EQ(-4242, r.GetInteger64(1));
+
+      r.Next();
+      ASSERT_TRUE(r.IsDone());
+    }
+
+    {
+      t.BindInteger(0, 40);
+      PostgreSQLResult r(t);
+      ASSERT_TRUE(r.IsDone());
+    }
+  }
+  
+}
+
+
+TEST(PostgreSQL, String)
+{
+  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
+
+  pg->Execute("CREATE TABLE Test(name INTEGER, value VARCHAR(40))");
+
+  PostgreSQLStatement s(*pg, "INSERT INTO Test VALUES ($1,$2)", false);
+  s.DeclareInputInteger(0);
+  s.DeclareInputString(1);
+
+  s.BindInteger(0, 42);
+  s.BindString(1, "Hello");
+  s.Run();
+
+  s.BindInteger(0, 43);
+  s.BindNull(1);
+  s.Run();
+
+  s.BindNull(0);
+  s.BindString(1, "");
+  s.Run();
+
+  {
+    PostgreSQLStatement t(*pg, "SELECT name, value FROM Test ORDER BY name", true);
+    PostgreSQLResult r(t);
+
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_FALSE(r.IsNull(0)); ASSERT_EQ(42, r.GetInteger(0));
+    ASSERT_FALSE(r.IsNull(1)); ASSERT_EQ("Hello", r.GetString(1));
+
+    r.Next();
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_FALSE(r.IsNull(0)); ASSERT_EQ(43, r.GetInteger(0));
+    ASSERT_TRUE(r.IsNull(1));
+
+    r.Next();
+    ASSERT_FALSE(r.IsDone());
+    ASSERT_TRUE(r.IsNull(0));
+    ASSERT_FALSE(r.IsNull(1)); ASSERT_EQ("", r.GetString(1));
+
+    r.Next();
+    ASSERT_TRUE(r.IsDone());
+  }
+}
+
+
+TEST(PostgreSQL, Transaction)
+{
+  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
+
+  pg->Execute("CREATE TABLE Test(name INTEGER, value INTEGER)");
+
+  {
+    PostgreSQLStatement s(*pg, "INSERT INTO Test VALUES ($1,$2)", false);
+    s.DeclareInputInteger(0);
+    s.DeclareInputInteger(1);
+    s.BindInteger(0, 42);
+    s.BindInteger(1, 4242);
+    s.Run();
+
+    {
+      PostgreSQLTransaction t(*pg);
+      s.BindInteger(0, 43);
+      s.BindInteger(1, 4343);
+      s.Run();
+      s.BindInteger(0, 44);
+      s.BindInteger(1, 4444);
+      s.Run();
+
+      PostgreSQLStatement u(*pg, "SELECT COUNT(*) FROM Test", true);
+      PostgreSQLResult r(u);
+      ASSERT_EQ(3, r.GetInteger64(0));
+
+      // No commit
+    }
+
+    {
+      PostgreSQLStatement u(*pg, "SELECT COUNT(*) FROM Test", true);
+      PostgreSQLResult r(u);
+      ASSERT_EQ(1, r.GetInteger64(0));  // Just "1" because of implicit rollback
+    }
+    
+    {
+      PostgreSQLTransaction t(*pg);
+      s.BindInteger(0, 43);
+      s.BindInteger(1, 4343);
+      s.Run();
+      s.BindInteger(0, 44);
+      s.BindInteger(1, 4444);
+      s.Run();
+
+      {
+        PostgreSQLStatement u(*pg, "SELECT COUNT(*) FROM Test", true);
+        PostgreSQLResult r(u);
+        ASSERT_EQ(3, r.GetInteger64(0));
+
+        t.Commit();
+        ASSERT_THROW(t.Rollback(), Orthanc::OrthancException);
+        ASSERT_THROW(t.Commit(), Orthanc::OrthancException);
+      }
+    }
+
+    {
+      PostgreSQLStatement u(*pg, "SELECT COUNT(*) FROM Test", true);
+      PostgreSQLResult r(u);
+      ASSERT_EQ(3, r.GetInteger64(0));
+    }
+  }
+}
+
+
+
+
+
+TEST(PostgreSQL, LargeObject)
+{
+  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
+  ASSERT_EQ(0, CountLargeObjects(*pg));
+
+  pg->Execute("CREATE TABLE Test(name VARCHAR, value OID)");
+
+  // Automatically remove the large objects associated with the table
+  pg->Execute("CREATE RULE TestDelete AS ON DELETE TO Test DO SELECT lo_unlink(old.value);");
+
+  {
+    PostgreSQLStatement s(*pg, "INSERT INTO Test VALUES ($1,$2)", false);
+    s.DeclareInputString(0);
+    s.DeclareInputLargeObject(1);
+    
+    for (int i = 0; i < 10; i++)
+    {
+      PostgreSQLTransaction t(*pg);
+
+      std::string value = "Value " + boost::lexical_cast<std::string>(i * 2);
+      PostgreSQLLargeObject obj(*pg, value);
+
+      s.BindString(0, "Index " + boost::lexical_cast<std::string>(i));
+      s.BindLargeObject(1, obj);
+      s.Run();
+
+      std::string tmp;
+      PostgreSQLLargeObject::Read(tmp, *pg, obj.GetOid());
+      ASSERT_EQ(value, tmp);
+
+      t.Commit();
+    }
+  }
+
+
+  ASSERT_EQ(10, CountLargeObjects(*pg));
+
+  {
+    PostgreSQLTransaction t(*pg);
+    PostgreSQLStatement s(*pg, "SELECT * FROM Test ORDER BY name DESC", true);
+    PostgreSQLResult r(s);
+
+    ASSERT_FALSE(r.IsDone());
+
+    ASSERT_FALSE(r.IsNull(0));
+    ASSERT_EQ("Index 9", r.GetString(0));
+
+    std::string data;
+    r.GetLargeObject(data, 1);
+    ASSERT_EQ("Value 18", data);    
+
+    r.Next();
+    ASSERT_FALSE(r.IsDone());
+
+    //ASSERT_TRUE(r.IsString(0));
+  }
+
+
+  {
+    PostgreSQLTransaction t(*pg);
+    PostgreSQLStatement s(*pg, "DELETE FROM Test WHERE name='Index 9'", false);
+    s.Run();
+    t.Commit();
+  }
+
+
+  {
+    // Count the number of items in the DB
+    PostgreSQLTransaction t(*pg);
+    PostgreSQLStatement s(*pg, "SELECT COUNT(*) FROM Test", true);
+    PostgreSQLResult r(s);
+    ASSERT_EQ(9, r.GetInteger64(0));
+  }
+
+  ASSERT_EQ(9, CountLargeObjects(*pg));
+}
+
+
+
+#if ORTHANC_POSTGRESQL_STATIC == 1
+#  include <c.h>  // PostgreSQL includes
+
+TEST(PostgreSQL, Version)
+{
+  ASSERT_STREQ("9.6.1", PG_VERSION);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/UnitTests/UnitTestsMain.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,171 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Plugins/PostgreSQLIndex.h"
+
+#include <Core/Logging.h>
+#include <gtest/gtest.h>
+
+OrthancDatabases::PostgreSQLParameters  globalParameters_;
+
+#include "../../Framework/Plugins/IndexUnitTests.h"
+
+
+TEST(PostgreSQLParameters, Basic)
+{
+  OrthancDatabases::PostgreSQLParameters p;
+  p.SetDatabase("world");
+
+  ASSERT_EQ("postgresql://localhost:5432/world", p.GetConnectionUri());
+
+  p.ResetDatabase();
+  ASSERT_EQ("postgresql://localhost:5432/", p.GetConnectionUri());
+
+  p.SetDatabase("hello");
+  ASSERT_EQ("postgresql://localhost:5432/hello", p.GetConnectionUri());
+
+  p.SetHost("server");
+  ASSERT_EQ("postgresql://server:5432/hello", p.GetConnectionUri());
+
+  p.SetPortNumber(1234);
+  ASSERT_EQ("postgresql://server:1234/hello", p.GetConnectionUri());
+
+  p.SetPortNumber(5432);
+  ASSERT_EQ("postgresql://server:5432/hello", p.GetConnectionUri());
+
+  p.SetUsername("user");
+  p.SetPassword("pass");
+  ASSERT_EQ("postgresql://user:pass@server:5432/hello", p.GetConnectionUri());
+
+  p.SetPassword("");
+  ASSERT_EQ("postgresql://user@server:5432/hello", p.GetConnectionUri());
+
+  p.SetUsername("");
+  p.SetPassword("pass");
+  ASSERT_EQ("postgresql://server:5432/hello", p.GetConnectionUri());
+
+  p.SetUsername("");
+  p.SetPassword("");
+  ASSERT_EQ("postgresql://server:5432/hello", p.GetConnectionUri());
+
+  p.SetConnectionUri("hello://world");
+  ASSERT_EQ("hello://world", p.GetConnectionUri());
+}
+
+
+TEST(PostgreSQLIndex, Lock)
+{
+  OrthancDatabases::PostgreSQLParameters noLock = globalParameters_;
+  noLock.SetLock(false);
+
+  OrthancDatabases::PostgreSQLParameters lock = globalParameters_;
+  lock.SetLock(true);
+
+  OrthancDatabases::PostgreSQLIndex db1(noLock);
+  db1.SetClearAll(true);
+  db1.Open();
+
+  {
+    OrthancDatabases::PostgreSQLIndex db2(lock);
+    db2.Open();
+
+    OrthancDatabases::PostgreSQLIndex db3(lock);
+    ASSERT_THROW(db3.Open(), Orthanc::OrthancException);
+  }
+
+  OrthancDatabases::PostgreSQLIndex db4(lock);
+  db4.Open();
+}
+
+
+#if 0
+TEST(PostgreSQL, StorageArea)
+{
+  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
+  PostgreSQLStorageArea s(pg.release(), true, true);
+
+  ASSERT_EQ(0, CountLargeObjects(s.GetDatabase()));
+  
+  for (int i = 0; i < 10; i++)
+  {
+    std::string uuid = boost::lexical_cast<std::string>(i);
+    std::string value = "Value " + boost::lexical_cast<std::string>(i * 2);
+    s.Create(uuid, value.c_str(), value.size(), OrthancPluginContentType_Unknown);
+  }
+
+  std::string tmp;
+  ASSERT_THROW(s.Read(tmp, "nope", OrthancPluginContentType_Unknown), Orthanc::OrthancException);
+  
+  ASSERT_EQ(10, CountLargeObjects(s.GetDatabase()));
+  s.Remove("5", OrthancPluginContentType_Unknown);
+  ASSERT_EQ(9, CountLargeObjects(s.GetDatabase()));
+
+  for (int i = 0; i < 10; i++)
+  {
+    std::string uuid = boost::lexical_cast<std::string>(i);
+    std::string expected = "Value " + boost::lexical_cast<std::string>(i * 2);
+    std::string content;
+
+    if (i == 5)
+    {
+      ASSERT_THROW(s.Read(content, uuid, OrthancPluginContentType_Unknown), Orthanc::OrthancException);
+    }
+    else
+    {
+      s.Read(content, uuid, OrthancPluginContentType_Unknown);
+      ASSERT_EQ(expected, content);
+    }
+  }
+
+  s.Clear();
+  ASSERT_EQ(0, CountLargeObjects(s.GetDatabase()));
+}
+#endif
+
+
+int main(int argc, char **argv)
+{
+  if (argc < 6)
+  {
+    std::cerr << "Usage: " << argv[0] << " <host> <port> <username> <password> <database>"
+              << std::endl << std::endl
+              << "Example: " << argv[0] << " localhost 5432 postgres postgres orthanctest"
+              << std::endl << std::endl;
+    return -1;
+  }
+
+  globalParameters_.SetHost(argv[1]);
+  globalParameters_.SetPortNumber(boost::lexical_cast<uint16_t>(argv[2]));
+  globalParameters_.SetUsername(argv[3]);
+  globalParameters_.SetPassword(argv[4]);
+  globalParameters_.SetDatabase(argv[5]);
+
+  ::testing::InitGoogleTest(&argc, argv);
+  Orthanc::Logging::Initialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+  Orthanc::Logging::EnableTraceLevel(true);
+
+  int result = RUN_ALL_TESTS();
+
+  Orthanc::Logging::Finalize();
+
+  return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,63 @@
+Database plugins for Orthanc
+============================
+
+
+General Information
+-------------------
+
+This repository contains the source code of various database plugins
+for Orthanc, the lightweight, RESTful DICOM server. These plugins
+enable Orthanc to store its index and its storage area within
+well-known relational databases systems (RDBMS).
+
+
+Content
+-------
+
+* ./Framework/  : code shared by all the plugins
+* ./MySQL/      : index and storage plugins for MySQL
+* ./PostgreSQL/ : index and storage plugins for PostgreSQL
+* ./SQLite/     : index plugin for SQLite (for experimentation)
+
+If you downloaded this project as a versioned release package
+(.tar.gz) focused on one given RDBMS, you will only find the folders
+that are related to this specific RDBMS. The full source code is
+available at:
+https://bitbucket.org/sjodogne/orthanc-databases/
+
+
+Older releases of PostgreSQL
+----------------------------
+
+This repository supersedes the older "orthanc-postgresql" repository
+that was only focused on PostgreSQL.
+
+Releases <= 2.1 of the PostgreSQL plugins can still be found at:
+https://bitbucket.org/sjodogne/orthanc-postgresql/
+
+
+Licensing
+---------
+
+The database plugins for Orthanc are licensed under the AGPL license.
+
+We also kindly ask scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated publications.
+Similarly, we ask open-source and closed-source products that make
+use of Orthanc to warn us about this use. You can cite our work
+using the following BibTeX entry:
+
+@Article{Jodogne2018,
+  author="Jodogne, S{\'e}bastien",
+  title="The {O}rthanc Ecosystem for Medical Imaging",
+  journal="Journal of Digital Imaging",
+  year="2018",
+  month="Jun",
+  day="01",
+  volume="31",
+  number="3",
+  pages="341--352",
+  issn="1618-727X",
+  doi="10.1007/s10278-018-0082-y",
+  url="https://doi.org/10.1007/s10278-018-0082-y"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DatabasesFrameworkConfiguration.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,130 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Enable the Orthanc subcomponents depending on the configuration
+#####################################################################
+
+if (ENABLE_SQLITE_BACKEND)
+  set(ENABLE_SQLITE ON)
+endif()
+
+if (ENABLE_POSTGRESQL_BACKEND)
+  set(ENABLE_SSL ON)
+  set(ENABLE_ZLIB ON)
+endif()
+
+if (ENABLE_MYSQL_BACKEND)
+  set(ENABLE_CRYPTO_OPTIONS ON)
+  set(ENABLE_SSL ON)
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+    set(ENABLE_LOCALE ON)           # iconv is needed
+    set(ENABLE_OPENSSL_ENGINES ON)
+    set(ENABLE_WEB_CLIENT ON)       # libcurl is needed if targetting Windows
+  endif()
+endif()
+
+
+#####################################################################
+## Configure the Orthanc Framework
+#####################################################################
+
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
+include_directories(${ORTHANC_ROOT})
+
+
+#####################################################################
+## Common source files for the databases
+#####################################################################
+
+set(ORTHANC_DATABASES_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
+
+set(DATABASES_SOURCES
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/BinaryStringValue.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/DatabaseManager.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/Dictionary.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/FileValue.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/GenericFormatter.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/Integer64Value.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/NullValue.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/Query.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/ResultBase.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/StatementLocation.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Common/Utf8StringValue.cpp
+  )
+
+
+#####################################################################
+## Configure SQLite if need be
+#####################################################################
+
+if (ENABLE_SQLITE_BACKEND)
+  list(APPEND DATABASES_SOURCES
+    ${ORTHANC_DATABASES_ROOT}/Framework/SQLite/SQLiteDatabase.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/SQLite/SQLiteResult.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/SQLite/SQLiteStatement.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/SQLite/SQLiteTransaction.cpp
+    )
+endif()
+
+
+#####################################################################
+## Configure MySQL client (MariaDB) if need be
+#####################################################################
+
+if (ENABLE_MYSQL_BACKEND)
+  include(${CMAKE_CURRENT_LIST_DIR}/MariaDBConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_MYSQL=1)
+  list(APPEND DATABASES_SOURCES
+    ${ORTHANC_DATABASES_ROOT}/Framework/MySQL/MySQLDatabase.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/MySQL/MySQLParameters.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/MySQL/MySQLResult.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/MySQL/MySQLStatement.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/MySQL/MySQLTransaction.cpp
+    ${MYSQL_CLIENT_SOURCES}
+    )
+else()
+  unset(USE_SYSTEM_MYSQL_CLIENT CACHE)
+  add_definitions(-DORTHANC_ENABLE_MYSQL=0)
+endif()
+
+
+
+#####################################################################
+## Configure PostgreSQL client if need be
+#####################################################################
+
+if (ENABLE_POSTGRESQL_BACKEND)
+  include(${CMAKE_CURRENT_LIST_DIR}/PostgreSQLConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_POSTGRESQL=1)
+  list(APPEND DATABASES_SOURCES
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLDatabase.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLLargeObject.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLParameters.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLResult.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLStatement.cpp
+    ${ORTHANC_DATABASES_ROOT}/Framework/PostgreSQL/PostgreSQLTransaction.cpp
+    ${LIBPQ_SOURCES}
+    )
+else()
+  unset(USE_SYSTEM_LIBPQ CACHE)
+  add_definitions(-DORTHANC_ENABLE_POSTGRESQL=0)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DatabasesFrameworkParameters.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,44 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#####################################################################
+## Import the parameters of the Orthanc Framework
+#####################################################################
+
+include(${CMAKE_CURRENT_LIST_DIR}/../../Resources/Orthanc/DownloadOrthancFramework.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+
+
+#####################################################################
+## CMake parameters tunable by the user
+#####################################################################
+
+set(USE_SYSTEM_LIBPQ ON CACHE BOOL "Use the system version of the PostgreSQL client library")
+set(USE_SYSTEM_MYSQL_CLIENT ON CACHE BOOL "Use the system version of the MySQL client library")
+
+
+#####################################################################
+## Internal CMake parameters to enable the optional subcomponents of
+## the database engines
+#####################################################################
+
+set(ENABLE_MYSQL_BACKEND OFF)
+set(ENABLE_POSTGRESQL_BACKEND OFF)
+set(ENABLE_SQLITE_BACKEND OFF)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DatabasesPluginConfiguration.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,46 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesFrameworkConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  if (ORTHANC_SDK_VERSION STREQUAL "0.9.5")
+    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5)
+  elseif (ORTHANC_SDK_VERSION STREQUAL "framework")
+    include_directories(${ORTHANC_ROOT}/Plugins/Include)
+  else()
+    message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}")
+  endif()
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCppDatabasePlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+list(APPEND DATABASES_SOURCES
+  ${ORTHANC_CORE_SOURCES}
+  ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/GlobalProperties.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DatabasesPluginParameters.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,33 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+set(ALLOW_DOWNLOADS ON CACHE BOOL "Allow CMake to download packages")
+set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")")
+set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"")
+set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"")
+
+# Advanced parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+set(ORTHANC_SDK_VERSION "0.9.5" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\" or \"framework\"")
+
+include(${CMAKE_CURRENT_LIST_DIR}/DatabasesFrameworkParameters.cmake)
+
+set(ENABLE_GOOGLE_TEST ON)
+set(HAS_EMBEDDED_RESOURCES ON)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/FindPostgreSQL.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,172 @@
+# - Find the PostgreSQL installation.
+# In Windows, we make the assumption that, if the PostgreSQL files are installed, the default directory
+# will be C:\Program Files\PostgreSQL.
+#
+# This module defines
+#  PostgreSQL_LIBRARIES - the PostgreSQL libraries needed for linking
+#  PostgreSQL_INCLUDE_DIRS - the directories of the PostgreSQL headers
+#  PostgreSQL_VERSION_STRING - the version of PostgreSQL found (since CMake 2.8.8)
+
+#=============================================================================
+# Copyright 2004-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+# ----------------------------------------------------------------------------
+# History:
+# This module is derived from the module originally found in the VTK source tree.
+#
+# ----------------------------------------------------------------------------
+# Note:
+# PostgreSQL_ADDITIONAL_VERSIONS is a variable that can be used to set the
+# version mumber of the implementation of PostgreSQL.
+# In Windows the default installation of PostgreSQL uses that as part of the path.
+# E.g C:\Program Files\PostgreSQL\8.4.
+# Currently, the following version numbers are known to this module:
+# "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0"
+#
+# To use this variable just do something like this:
+# set(PostgreSQL_ADDITIONAL_VERSIONS "9.2" "8.4.4")
+# before calling find_package(PostgreSQL) in your CMakeLists.txt file.
+# This will mean that the versions you set here will be found first in the order
+# specified before the default ones are searched.
+#
+# ----------------------------------------------------------------------------
+# You may need to manually set:
+#  PostgreSQL_INCLUDE_DIR  - the path to where the PostgreSQL include files are.
+#  PostgreSQL_LIBRARY_DIR  - The path to where the PostgreSQL library files are.
+# If FindPostgreSQL.cmake cannot find the include files or the library files.
+#
+# ----------------------------------------------------------------------------
+# The following variables are set if PostgreSQL is found:
+#  PostgreSQL_FOUND         - Set to true when PostgreSQL is found.
+#  PostgreSQL_INCLUDE_DIRS  - Include directories for PostgreSQL
+#  PostgreSQL_LIBRARY_DIRS  - Link directories for PostgreSQL libraries
+#  PostgreSQL_LIBRARIES     - The PostgreSQL libraries.
+#
+# ----------------------------------------------------------------------------
+# If you have installed PostgreSQL in a non-standard location.
+# (Please note that in the following comments, it is assumed that <Your Path>
+# points to the root directory of the include directory of PostgreSQL.)
+# Then you have three options.
+# 1) After CMake runs, set PostgreSQL_INCLUDE_DIR to <Your Path>/include and
+#    PostgreSQL_LIBRARY_DIR to wherever the library pq (or libpq in windows) is
+# 2) Use CMAKE_INCLUDE_PATH to set a path to <Your Path>/PostgreSQL<-version>. This will allow find_path()
+#    to locate PostgreSQL_INCLUDE_DIR by utilizing the PATH_SUFFIXES option. e.g. In your CMakeLists.txt file
+#    set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "<Your Path>/include")
+# 3) Set an environment variable called ${PostgreSQL_ROOT} that points to the root of where you have
+#    installed PostgreSQL, e.g. <Your Path>.
+#
+# ----------------------------------------------------------------------------
+
+set(PostgreSQL_INCLUDE_PATH_DESCRIPTION "top-level directory containing the PostgreSQL include directories. E.g /usr/local/include/PostgreSQL/8.4 or C:/Program Files/PostgreSQL/8.4/include")
+set(PostgreSQL_INCLUDE_DIR_MESSAGE "Set the PostgreSQL_INCLUDE_DIR cmake cache entry to the ${PostgreSQL_INCLUDE_PATH_DESCRIPTION}")
+set(PostgreSQL_LIBRARY_PATH_DESCRIPTION "top-level directory containing the PostgreSQL libraries.")
+set(PostgreSQL_LIBRARY_DIR_MESSAGE "Set the PostgreSQL_LIBRARY_DIR cmake cache entry to the ${PostgreSQL_LIBRARY_PATH_DESCRIPTION}")
+set(PostgreSQL_ROOT_DIR_MESSAGE "Set the PostgreSQL_ROOT system variable to where PostgreSQL is found on the machine E.g C:/Program Files/PostgreSQL/8.4")
+
+
+set(PostgreSQL_KNOWN_VERSIONS ${PostgreSQL_ADDITIONAL_VERSIONS}
+    "10" "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0")
+
+# Define additional search paths for root directories.
+if ( WIN32 )
+  foreach (suffix ${PostgreSQL_KNOWN_VERSIONS} )
+    set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${PostgreSQL_ADDITIONAL_SEARCH_PATHS} "C:/Program Files/PostgreSQL/${suffix}" )
+  endforeach()
+else()
+  foreach (suffix ${PostgreSQL_KNOWN_VERSIONS} )
+    set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${PostgreSQL_ADDITIONAL_SEARCH_PATHS} "/usr/include/postgresql/${suffix}" "/usr/local/include/postgresql/${suffix}")
+  endforeach()
+endif()
+set( PostgreSQL_ROOT_DIRECTORIES
+   ENV PostgreSQL_ROOT
+   ${PostgreSQL_ROOT}
+   ${PostgreSQL_ADDITIONAL_SEARCH_PATHS}
+)
+
+#
+# Look for an installation.
+#
+find_path(PostgreSQL_INCLUDE_DIR
+  NAMES libpq-fe.h
+  PATHS
+   # Look in other places.
+   ${PostgreSQL_ROOT_DIRECTORIES}
+  PATH_SUFFIXES
+    pgsql
+    postgresql
+    include
+  # Help the user find it if we cannot.
+  DOC "The ${PostgreSQL_INCLUDE_DIR_MESSAGE}"
+)
+
+find_path(PostgreSQL_TYPE_INCLUDE_DIR
+  NAMES catalog/pg_type.h
+  PATHS
+   # Look in other places.
+   ${PostgreSQL_ROOT_DIRECTORIES}
+  PATH_SUFFIXES
+    postgresql
+    pgsql/server
+    postgresql/server
+    include/server
+    server
+  # Help the user find it if we cannot.
+  DOC "The ${PostgreSQL_INCLUDE_DIR_MESSAGE}"
+  )
+
+# The PostgreSQL library.
+set (PostgreSQL_LIBRARY_TO_FIND pq)
+# Setting some more prefixes for the library
+set (PostgreSQL_LIB_PREFIX "")
+if ( WIN32 )
+  set (PostgreSQL_LIB_PREFIX ${PostgreSQL_LIB_PREFIX} "lib")
+  set ( PostgreSQL_LIBRARY_TO_FIND ${PostgreSQL_LIB_PREFIX}${PostgreSQL_LIBRARY_TO_FIND})
+endif()
+
+find_library( PostgreSQL_LIBRARY
+ NAMES ${PostgreSQL_LIBRARY_TO_FIND}
+ PATHS
+   ${PostgreSQL_ROOT_DIRECTORIES}
+ PATH_SUFFIXES
+   lib
+)
+get_filename_component(PostgreSQL_LIBRARY_DIR ${PostgreSQL_LIBRARY} PATH)
+
+if (PostgreSQL_INCLUDE_DIR AND EXISTS "${PostgreSQL_INCLUDE_DIR}/pg_config.h")
+  file(STRINGS "${PostgreSQL_INCLUDE_DIR}/pg_config.h" pgsql_version_str
+       REGEX "^#define[\t ]+PG_VERSION[\t ]+\".*\"")
+
+  string(REGEX REPLACE "^#define[\t ]+PG_VERSION[\t ]+\"([^\"]*)\".*" "\\1"
+         PostgreSQL_VERSION_STRING "${pgsql_version_str}")
+  unset(pgsql_version_str)
+endif()
+
+# Did we find anything?
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(PostgreSQL
+                                  REQUIRED_VARS PostgreSQL_LIBRARY PostgreSQL_INCLUDE_DIR PostgreSQL_TYPE_INCLUDE_DIR
+                                  VERSION_VAR PostgreSQL_VERSION_STRING)
+set( PostgreSQL_FOUND  ${POSTGRESQL_FOUND})
+
+# Now try to get the include and library path.
+if(PostgreSQL_FOUND)
+
+  set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR} ${PostgreSQL_TYPE_INCLUDE_DIR} )
+  set(PostgreSQL_LIBRARY_DIRS ${PostgreSQL_LIBRARY_DIR} )
+  set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY_TO_FIND})
+  #message("Final PostgreSQL include dir: ${PostgreSQL_INCLUDE_DIRS}")
+  #message("Final PostgreSQL library dir: ${PostgreSQL_LIBRARY_DIRS}")
+  #message("Final PostgreSQL libraries:   ${PostgreSQL_LIBRARIES}")
+endif()
+
+mark_as_advanced(PostgreSQL_INCLUDE_DIR PostgreSQL_TYPE_INCLUDE_DIR PostgreSQL_LIBRARY )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/MariaDBConfiguration.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,161 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_MYSQL_CLIENT)
+  set(MARIADB_CLIENT_VERSION_MAJOR "10")
+  set(MARIADB_CLIENT_VERSION_MINOR "3")
+  set(MARIADB_CLIENT_VERSION_PATCH "6")
+  set(MARIADB_PACKAGE_VERSION "3.0.5")
+  set(MARIADB_CLIENT_SOURCES_DIR ${CMAKE_BINARY_DIR}/mariadb-connector-c-${MARIADB_PACKAGE_VERSION}-src)
+  set(MARIADB_CLIENT_MD5 "b846584b8b7a39c51a6e83986b57c71c")
+  set(MARIADB_CLIENT_URL "http://www.orthanc-server.com/downloads/third-party/mariadb-connector-c-${MARIADB_PACKAGE_VERSION}-src.tar.gz")
+
+  if (IS_DIRECTORY "${MARIADB_CLIENT_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${MARIADB_CLIENT_MD5} ${MARIADB_CLIENT_URL} "${MARIADB_CLIENT_SOURCES_DIR}")
+
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${CMAKE_CURRENT_LIST_DIR}/../MariaDB/mariadb-connector-c-3.0.5.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
+
+  include(${MARIADB_CLIENT_SOURCES_DIR}/cmake/CheckIncludeFiles.cmake)
+  include(${MARIADB_CLIENT_SOURCES_DIR}/cmake/CheckFunctions.cmake)
+  include(${MARIADB_CLIENT_SOURCES_DIR}/cmake/CheckTypes.cmake)
+
+  set(MARIADB_CLIENT_VERSION "${MARIADB_CLIENT_VERSION_MAJOR}.${MARIADB_CLIENT_VERSION_MINOR}.${MARIADB_CLIENT_VERSION_PATCH}")
+  set(MARIADB_BASE_VERSION "mariadb-${MARIADB_CLIENT_VERSION_MAJOR}.${MARIADB_CLIENT_VERSION_MINOR}")
+  math(EXPR MARIADB_VERSION_ID "${MARIADB_CLIENT_VERSION_MAJOR} * 10000 +
+                              ${MARIADB_CLIENT_VERSION_MINOR} * 100   +
+                              ${MARIADB_CLIENT_VERSION_PATCH}")
+
+  add_definitions(
+    -DHAVE_OPENSSL=1
+    -DHAVE_TLS=1
+    -DHAVE_REMOTEIO=1
+    -DHAVE_COMPRESS=1
+    -DLIBMARIADB
+    -DTHREAD
+    )
+
+  set(HAVE_DLOPEN 1)
+  set(PROTOCOL_VERSION ${MARIADB_CLIENT_VERSION_MAJOR})
+  set(MARIADB_PORT 3306)
+  set(MARIADB_UNIX_ADDR "/var/run/mysqld/mysqld.sock")
+  set(DEFAULT_CHARSET "latin1")
+
+  FOREACH(plugin mysql_native_password mysql_old_password pvio_socket)
+    set(EXTERNAL_PLUGINS "${EXTERNAL_PLUGINS} extern struct st_mysql_client_plugin ${plugin}_client_plugin;\n")
+    set(BUILTIN_PLUGINS "${BUILTIN_PLUGINS}   (struct st_mysql_client_plugin *)&${plugin}_client_plugin,\n")
+  ENDFOREACH()
+
+  configure_file(
+    ${MARIADB_CLIENT_SOURCES_DIR}/include/ma_config.h.in
+    ${MARIADB_CLIENT_SOURCES_DIR}/include/ma_config.h
+    )
+
+  configure_file(
+    ${MARIADB_CLIENT_SOURCES_DIR}/include/mariadb_version.h.in
+    ${MARIADB_CLIENT_SOURCES_DIR}/include/mariadb_version.h
+    )
+
+  configure_file(
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_client_plugin.c.in
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_client_plugin.c
+    )
+
+  include_directories(
+    ${MARIADB_CLIENT_SOURCES_DIR}/include
+    )
+
+  set(MYSQL_CLIENT_SOURCES
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_alloc.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_array.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_charset.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_client_plugin.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_compress.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_context.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_default.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_dtoa.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_errmsg.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_hash.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_init.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_io.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_list.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_ll2str.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_loaddata.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_net.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_password.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_pvio.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_sha1.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_stmt_codec.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_string.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_time.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/ma_tls.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/mariadb_async.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/mariadb_charset.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/mariadb_dyncol.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/mariadb_lib.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/mariadb_stmt.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/libmariadb/secure/openssl.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/plugins/auth/my_auth.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/plugins/auth/old_password.c
+    ${MARIADB_CLIENT_SOURCES_DIR}/plugins/pvio/pvio_socket.c
+    )
+
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+    link_libraries(shlwapi)
+  endif()
+
+else()
+  find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h
+    /usr/local/include/mysql
+    /usr/include/mysql
+    )
+
+  if (MYSQLCLIENT_INCLUDE_DIR)
+    include_directories(${MYSQLCLIENT_INCLUDE_DIR})
+    set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}")
+  endif()
+
+  check_include_file(mysql.h HAVE_MYSQL_CLIENT_H)
+  if (NOT HAVE_MYSQL_CLIENT_H)
+    message(FATAL_ERROR "Please install the libmysqlclient-dev package")
+  endif()
+
+  check_library_exists(mysqlclient mysql_init "" HAVE_MYSQL_CLIENT_LIB)
+  if (NOT HAVE_MYSQL_CLIENT_LIB)
+    message(FATAL_ERROR "Unable to find the mysqlclient library")
+  endif()
+
+  link_libraries(mysqlclient)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/PostgreSQLConfiguration.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,365 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+#####################################################################
+## PostgreSQL
+#####################################################################
+
+INCLUDE(CheckTypeSize)
+INCLUDE(CheckCSourceCompiles)
+INCLUDE(CheckFunctionExists)
+INCLUDE(CheckStructHasMember)
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPQ)
+  add_definitions(-DORTHANC_POSTGRESQL_STATIC=1)
+
+  SET(LIBPQ_MAJOR 9)
+  SET(LIBPQ_MINOR 6)
+  SET(LIBPQ_REVISION 1)
+  SET(LIBPQ_VERSION ${LIBPQ_MAJOR}.${LIBPQ_MINOR}.${LIBPQ_REVISION})
+
+  SET(LIBPQ_SOURCES_DIR ${CMAKE_BINARY_DIR}/postgresql-${LIBPQ_VERSION})
+  DownloadPackage(
+    "eaa7e267e89ea1ed2693d2b88d3cd290"
+    "http://www.orthanc-server.com/downloads/third-party/postgresql-${LIBPQ_VERSION}.tar.gz"
+    "${LIBPQ_SOURCES_DIR}")
+
+  
+  ##
+  ## Platform-specific configuration
+  ##
+  
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    add_definitions(
+      -DEXEC_BACKEND
+      )
+
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/win32.h
+      ${AUTOGENERATED_DIR}/pg_config_os.h
+      COPYONLY)
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    add_definitions(
+      -D_GNU_SOURCE
+      -D_THREAD_SAFE
+      -D_POSIX_PTHREAD_SEMANTICS
+      )
+
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/linux.h
+      ${AUTOGENERATED_DIR}/pg_config_os.h
+      COPYONLY)
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+    add_definitions(
+      -D_GNU_SOURCE
+      -D_THREAD_SAFE
+      -D_POSIX_PTHREAD_SEMANTICS
+      )
+
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/darwin.h
+      ${AUTOGENERATED_DIR}/pg_config_os.h
+      COPYONLY)
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/openbsd.h
+      ${AUTOGENERATED_DIR}/pg_config_os.h
+      COPYONLY)
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/freebsd.h
+      ${AUTOGENERATED_DIR}/pg_config_os.h
+      COPYONLY)
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+
+
+  ##
+  ## Generation of "pg_config.h"
+  ## 
+  
+  if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/pg_config_ext.h.win32
+      ${AUTOGENERATED_DIR}/pg_config_ext.h
+      COPYONLY)
+
+    configure_file(
+      ${LIBPQ_SOURCES_DIR}/src/include/pg_config.h.win32
+      ${AUTOGENERATED_DIR}/pg_config.h
+      COPYONLY)
+
+    if (CMAKE_COMPILER_IS_GNUCXX)  # MinGW
+      add_definitions(
+        -DPG_PRINTF_ATTRIBUTE=gnu_printf
+        -DHAVE_GETTIMEOFDAY
+        -DHAVE_LONG_LONG_INT_64
+        -DHAVE_STRUCT_ADDRINFO
+        -DHAVE_STRUCT_SOCKADDR_STORAGE
+        -DHAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY
+        )
+    endif()
+    
+  elseif(CROSS_COMPILING)
+    message(FATAL_ERROR "Cannot auto-generate the configuration file cross-compiling")
+    
+  else()
+    configure_file(
+      ${CMAKE_CURRENT_LIST_DIR}/../PostgreSQL/pg_config_ext.h
+      ${AUTOGENERATED_DIR}/pg_config_ext.h
+      COPYONLY
+      )
+
+    set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h;netdb.h;sys/types.h")
+
+    include(${CMAKE_CURRENT_LIST_DIR}/../PostgreSQL/func_accept_args.cmake)
+    set(ACCEPT_TYPE_ARG3 ${ACCEPT_TYPE_ARG3})
+
+    check_type_size("long int" SIZE_LONG_INT)
+    if (SIZE_LONG_INT EQUAL 8)
+      set(HAVE_LONG_INT_64 1)
+    endif()
+
+    check_type_size("long long int" SIZE_LONG_LONG_INT)
+    if (SIZE_LONG_LONG_INT EQUAL 8)
+      set(HAVE_LONG_LONG_INT_64 1)
+    endif()
+
+    file(READ ${CMAKE_CURRENT_LIST_DIR}/../PostgreSQL/c_flexmember.c SOURCE)
+    check_c_source_compiles("${SOURCE}" c_flexmember)
+    if (c_flexmember)
+      set(FLEXIBLE_ARRAY_MEMBER "/**/")
+    endif()
+
+    if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
+        CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
+        CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+      set(PG_PRINTF_ATTRIBUTE "printf")
+    else()
+      file(READ ${CMAKE_CURRENT_LIST_DIR}/../PostgreSQL/printf_archetype.c SOURCE)
+      check_c_source_compiles("${SOURCE}" printf_archetype)
+      if (printf_archetype)
+        set(PG_PRINTF_ATTRIBUTE "gnu_printf")
+      else()
+        set(PG_PRINTF_ATTRIBUTE "printf")
+      endif()
+    endif()
+
+    check_function_exists("isinf" HAVE_ISINF)
+    check_function_exists("getaddrinfo" HAVE_GETADDRINFO)
+    check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY)
+    check_function_exists("snprintf" HAVE_DECL_SNPRINTF)
+    check_function_exists("srandom" HAVE_SRANDOM)
+    check_function_exists("strlcat" HAVE_DECL_STRLCAT)
+    check_function_exists("strlcpy" HAVE_DECL_STRLCPY)
+    check_function_exists("unsetenv" HAVE_UNSETENV)
+    check_function_exists("vsnprintf" HAVE_DECL_VSNPRINTF)
+
+    check_type_size("struct addrinfo" SIZE_STRUCT_ADDRINFO)
+    if (HAVE_SIZE_STRUCT_ADDRINFO)
+      set(HAVE_STRUCT_ADDRINFO 1)
+    endif()
+
+    check_type_size("struct sockaddr_storage" SIZE_STRUCT_SOCKADDR_STORAGE)
+    if (HAVE_SIZE_STRUCT_SOCKADDR_STORAGE)
+      set(HAVE_STRUCT_SOCKADDR_STORAGE 1)
+    endif()
+
+    set(MEMSET_LOOP_LIMIT 1024)            # This is hardcoded in "postgresql-9.6.1/configure"
+    set(DEF_PGPORT 5432)                   # Default port number of PostgreSQL
+    set(DEF_PGPORT_STR "\"5432\"")         # Same as above, as a string
+    set(PG_VERSION "\"${LIBPQ_VERSION}\"") # Version of PostgreSQL, as a string
+
+    # Version of PostgreSQL, as a number
+    math(EXPR PG_VERSION_NUM "${LIBPQ_MAJOR} * 10000 + ${LIBPQ_MINOR} * 100 + ${LIBPQ_REVISION}")
+    
+    set(HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1)   # TODO Autodetection
+
+    # Compute maximum alignment of any basic type.
+    # We assume long's alignment is at least as strong as char, short, or int;
+    # but we must check long long (if it exists) and double.
+    check_type_size("long" SIZE_LONG)
+    check_type_size("long long" SIZE_LONG_LONG)
+    check_type_size("double" SIZE_DOUBLE)
+    set(MAXIMUM_ALIGNOF ${SIZE_LONG})
+    if(SIZE_LONG_LONG AND SIZE_LONG_LONG GREATER MAXIMUM_ALIGNOF)
+      set(MAXIMUM_ALIGNOF ${SIZE_LONG_LONG})
+    endif()
+    if(SIZE_DOUBLE GREATER MAXIMUM_ALIGNOF)
+      set(MAXIMUM_ALIGNOF ${SIZE_DOUBLE})
+    endif()
+
+    check_include_file("poll.h" HAVE_POLL_H)
+    check_include_file("net/if.h" HAVE_NET_IF_H)
+    check_include_file("netinet/in.h" HAVE_NETINET_IN_H)
+    check_include_file("netinet/tcp.h" HAVE_NETINET_TCP_H)
+    check_include_file("sys/ioctl.h" HAVE_SYS_IOCTL_H)
+    check_include_file("sys/un.h" HAVE_SYS_UN_H)
+
+    If (NOT HAVE_NET_IF_H)  # This is the case of OpenBSD
+      unset(HAVE_NET_IF_H CACHE)
+      check_include_files("sys/socket.h;net/if.h" HAVE_NET_IF_H)
+    endif()
+
+    if (NOT HAVE_NETINET_TCP_H)  # This is the case of OpenBSD
+      unset(HAVE_NETINET_TCP_H CACHE)
+      check_include_files("sys/socket.h;netinet/tcp.h" HAVE_NETINET_TCP_H)
+    endif()
+
+
+    execute_process(
+      COMMAND 
+      ${PYTHON_EXECUTABLE}
+      "${CMAKE_CURRENT_LIST_DIR}/../PostgreSQL/PrepareCMakeConfigurationFile.py"
+      "${LIBPQ_SOURCES_DIR}/src/include/pg_config.h.in"
+      "${AUTOGENERATED_DIR}/pg_config.h.in"
+      ERROR_VARIABLE NO_PG_CONFIG
+      OUTPUT_VARIABLE out
+      )
+
+    if (NO_PG_CONFIG)
+      message(FATAL_ERROR "Cannot find pg_config.h.in")
+    endif()
+    
+    configure_file(
+      ${AUTOGENERATED_DIR}/pg_config.h.in
+      ${AUTOGENERATED_DIR}/pg_config.h)
+  endif()
+
+
+
+  ##
+  ## Generic configuration
+  ##
+
+  file(WRITE
+    ${AUTOGENERATED_DIR}/pg_config_paths.h
+    "")
+
+  add_definitions(
+    -D_REENTRANT
+    -DFRONTEND
+    -DUNSAFE_STAT_OK
+    -DSYSCONFDIR=""
+    )
+
+  include_directories(
+    ${LIBPQ_SOURCES_DIR}/src/include
+    ${LIBPQ_SOURCES_DIR}/src/include/libpq
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq
+    )
+
+  set(LIBPQ_SOURCES
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-auth.c 
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-connect.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-exec.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-lobj.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-misc.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-print.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol2.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-protocol3.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/fe-secure.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/libpq-events.c
+    ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/pqexpbuffer.c
+
+    # libpgport C files we always use
+    ${LIBPQ_SOURCES_DIR}/src/port/chklocale.c
+    ${LIBPQ_SOURCES_DIR}/src/port/inet_net_ntop.c
+    ${LIBPQ_SOURCES_DIR}/src/port/noblock.c
+    ${LIBPQ_SOURCES_DIR}/src/port/pgstrcasecmp.c
+    ${LIBPQ_SOURCES_DIR}/src/port/pqsignal.c
+    ${LIBPQ_SOURCES_DIR}/src/port/thread.c
+
+    ${LIBPQ_SOURCES_DIR}/src/backend/libpq/ip.c
+    ${LIBPQ_SOURCES_DIR}/src/backend/libpq/md5.c
+    ${LIBPQ_SOURCES_DIR}/src/backend/utils/mb/encnames.c
+    ${LIBPQ_SOURCES_DIR}/src/backend/utils/mb/wchar.c
+    )
+
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    LIST(APPEND LIBPQ_SOURCES
+      ${LIBPQ_SOURCES_DIR}/src/port/strlcpy.c
+      )      
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    include_directories(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/win32
+      ${LIBPQ_SOURCES_DIR}/src/port
+      )
+    
+    LIST(APPEND LIBPQ_SOURCES
+      # libpgport C files that are needed if identified by configure
+      ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/win32.c
+      ${LIBPQ_SOURCES_DIR}/src/port/crypt.c 
+      ${LIBPQ_SOURCES_DIR}/src/port/inet_aton.c
+      ${LIBPQ_SOURCES_DIR}/src/port/open.c
+      ${LIBPQ_SOURCES_DIR}/src/port/pgsleep.c
+      ${LIBPQ_SOURCES_DIR}/src/port/snprintf.c
+      ${LIBPQ_SOURCES_DIR}/src/port/system.c 
+      ${LIBPQ_SOURCES_DIR}/src/port/win32setlocale.c
+      ${LIBPQ_SOURCES_DIR}/src/port/getaddrinfo.c
+      ${LIBPQ_SOURCES_DIR}/src/port/strlcpy.c
+      )
+      
+    if (CMAKE_COMPILER_IS_GNUCXX OR 
+        (MSVC AND MSVC_VERSION GREATER 1800))
+      # Starting Visual Studio 2013 (version 1800), it is necessary to also add "win32error.c"
+      LIST(APPEND LIBPQ_SOURCES ${LIBPQ_SOURCES_DIR}/src/port/win32error.c)
+    endif()
+
+    if (MSVC)
+      LIST(APPEND LIBPQ_SOURCES ${LIBPQ_SOURCES_DIR}/src/interfaces/libpq/pthread-win32.c)
+    endif()
+  endif()
+
+  if (CMAKE_COMPILER_IS_GNUCXX AND
+      NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+    LIST(APPEND LIBPQ_SOURCES
+      ${LIBPQ_SOURCES_DIR}/src/port/getpeereid.c
+      )
+
+  elseif (MSVC)
+    include_directories(
+      ${LIBPQ_SOURCES_DIR}/src/include/port/win32_msvc
+      )
+    
+    LIST(APPEND LIBPQ_SOURCES
+      ${LIBPQ_SOURCES_DIR}/src/port/dirent.c 
+      ${LIBPQ_SOURCES_DIR}/src/port/dirmod.c 
+      )
+  endif()
+
+  source_group(ThirdParty\\PostgreSQL REGULAR_EXPRESSION ${LIBPQ_SOURCES_DIR}/.*)
+
+else()
+  include(${CMAKE_CURRENT_LIST_DIR}/FindPostgreSQL.cmake)
+  include_directories(
+    ${PostgreSQL_INCLUDE_DIR}
+    ${PostgreSQL_TYPE_INCLUDE_DIR}
+    )
+  link_libraries(${PostgreSQL_LIBRARY})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/MariaDB/mariadb-connector-c-3.0.5.patch	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,74 @@
+diff -urEb mariadb-connector-c-3.0.5-src.orig/include/ma_global.h mariadb-connector-c-3.0.5-src/include/ma_global.h
+--- mariadb-connector-c-3.0.5-src.orig/include/ma_global.h	2018-05-30 16:31:17.000000000 +0200
++++ mariadb-connector-c-3.0.5-src/include/ma_global.h	2018-06-12 17:08:52.578709929 +0200
+@@ -23,7 +23,7 @@
+ 
+ #ifdef _WIN32
+ #include <winsock2.h>
+-#include <Windows.h>
++#include <windows.h>
+ #include <stdlib.h>
+ #define strcasecmp _stricmp
+ #define sleep(x) Sleep(1000*(x))
+@@ -638,7 +638,9 @@
+ #error "Neither int or long is of 4 bytes width"
+ #endif
+ 
+-#if !defined(HAVE_ULONG) && !defined(HAVE_LINUXTHREADS) && !defined(__USE_MISC)
++#if defined(__LSB_VERSION__)
++typedef unsigned long	ulong;	/* Short for unsigned long */
++#elif !defined(HAVE_ULONG) && !defined(HAVE_LINUXTHREADS) && !defined(__USE_MISC)
+ typedef unsigned long	ulong;	/* Short for unsigned long */
+ #endif
+ #ifndef longlong_defined
+diff -urEb mariadb-connector-c-3.0.5-src.orig/libmariadb/ma_client_plugin.c.in mariadb-connector-c-3.0.5-src/libmariadb/ma_client_plugin.c.in
+--- mariadb-connector-c-3.0.5-src.orig/libmariadb/ma_client_plugin.c.in	2018-05-30 16:31:17.000000000 +0200
++++ mariadb-connector-c-3.0.5-src/libmariadb/ma_client_plugin.c.in	2018-06-12 16:34:45.402745736 +0200
+@@ -456,7 +456,7 @@
+ 
+ 
+ /* see <mysql/client_plugin.h> for a full description */
+-struct st_mysql_client_plugin * STDCALL
++struct st_mysql_client_plugin *
+ mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...)
+ {
+   struct st_mysql_client_plugin *p;
+diff -urEb mariadb-connector-c-3.0.5-src.orig/libmariadb/ma_default.c mariadb-connector-c-3.0.5-src/libmariadb/ma_default.c
+--- mariadb-connector-c-3.0.5-src.orig/libmariadb/ma_default.c	2018-05-30 16:31:17.000000000 +0200
++++ mariadb-connector-c-3.0.5-src/libmariadb/ma_default.c	2018-06-12 16:34:33.246745949 +0200
+@@ -27,7 +27,7 @@
+ 
+ #ifdef _WIN32
+ #include <io.h>
+-#include "Shlwapi.h"
++#include "shlwapi.h"
+ 
+ static const char *ini_exts[]= {"ini", "cnf", 0};
+ #define R_OK 4
+diff -urEb mariadb-connector-c-3.0.5-src.orig/libmariadb/mariadb_lib.c mariadb-connector-c-3.0.5-src/libmariadb/mariadb_lib.c
+--- mariadb-connector-c-3.0.5-src.orig/libmariadb/mariadb_lib.c	2018-05-30 16:31:17.000000000 +0200
++++ mariadb-connector-c-3.0.5-src/libmariadb/mariadb_lib.c	2018-06-12 16:34:33.246745949 +0200
+@@ -69,7 +69,7 @@
+ #endif
+ #include <mysql/client_plugin.h>
+ #ifdef _WIN32
+-#include "Shlwapi.h"
++#include "shlwapi.h"
+ #endif
+ 
+ #define ASYNC_CONTEXT_DEFAULT_STACK_SIZE (4096*15)
+diff -urEb mariadb-connector-c-3.0.5-src.orig/plugins/pvio/pvio_socket.c mariadb-connector-c-3.0.5-src/plugins/pvio/pvio_socket.c
+--- mariadb-connector-c-3.0.5-src.orig/plugins/pvio/pvio_socket.c	2018-05-30 16:31:17.000000000 +0200
++++ mariadb-connector-c-3.0.5-src/plugins/pvio/pvio_socket.c	2018-06-12 17:21:34.554696601 +0200
+@@ -60,6 +60,11 @@
+ #define IS_SOCKET_EINTR(err) 0
+ #endif
+ 
++#if defined(__LSB_VERSION__)
++// WARNING: This definition might break true Linux Standard Base compatibility!
++#define MSG_DONTWAIT 0x40 /* Nonblocking IO.  */
++#endif
++
+ #ifndef SOCKET_ERROR
+ #define SOCKET_ERROR -1
+ #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/DownloadOrthancFramework.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,323 @@
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2018 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# In addition, as a special exception, the copyright holders of this
+# program give permission to link the code of its release with the
+# OpenSSL project's "OpenSSL" library (or with modified versions of it
+# that use the same license as the "OpenSSL" library), and distribute
+# the linked executables. You must obey the GNU General Public License
+# in all respects for all of the code used other than "OpenSSL". If you
+# modify file(s) with this exception, you may extend this exception to
+# your version of the file(s), but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source files
+# in the program, then also delete it here.
+# 
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+
+##
+## Check whether the parent script sets the mandatory variables
+##
+
+if (NOT DEFINED ORTHANC_FRAMEWORK_SOURCE OR
+    (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "web" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" AND
+     NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "path"))
+  message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_SOURCE must be set to \"hg\", \"web\", \"archive\" or \"path\"")
+endif()
+
+
+##
+## Detection of the requested version
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_VERSION)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_VERSION must be set")
+  endif()
+
+  if (DEFINED ORTHANC_FRAMEWORK_MAJOR OR
+      DEFINED ORTHANC_FRAMEWORK_MINOR OR
+      DEFINED ORTHANC_FRAMEWORK_REVISION OR
+      DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Some internal variable has been set")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_MD5 "")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH)
+    if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline")
+      set(ORTHANC_FRAMEWORK_BRANCH "default")
+
+    else()
+      set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+      set(RE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
+      string(REGEX REPLACE ${RE} "\\1" ORTHANC_FRAMEWORK_MAJOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\2" ORTHANC_FRAMEWORK_MINOR ${ORTHANC_FRAMEWORK_VERSION})
+      string(REGEX REPLACE ${RE} "\\3" ORTHANC_FRAMEWORK_REVISION ${ORTHANC_FRAMEWORK_VERSION})
+
+      if (NOT ORTHANC_FRAMEWORK_MAJOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_MINOR MATCHES "^[0-9]+$" OR
+          NOT ORTHANC_FRAMEWORK_REVISION MATCHES "^[0-9]+$")
+        message("Bad version of the Orthanc framework: ${ORTHANC_FRAMEWORK_VERSION}")
+      endif()
+
+      if (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.1")
+        set(ORTHANC_FRAMEWORK_MD5 "dac95bd6cf86fb19deaf4e612961f378")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.3.2")
+        set(ORTHANC_FRAMEWORK_MD5 "d0ccdf68e855d8224331f13774992750")
+      endif()
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Detection of the third-party software
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  find_program(ORTHANC_FRAMEWORK_HG hg)
+  
+  if (${ORTHANC_FRAMEWORK_HG} MATCHES "ORTHANC_FRAMEWORK_HG-NOTFOUND")
+    message(FATAL_ERROR "Please install Mercurial")
+  endif()
+endif()
+
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    find_program(ORTHANC_FRAMEWORK_7ZIP 7z 
+      PATHS 
+      "$ENV{ProgramFiles}/7-Zip"
+      "$ENV{ProgramW6432}/7-Zip"
+      )
+
+    if (${ORTHANC_FRAMEWORK_7ZIP} MATCHES "ORTHANC_FRAMEWORK_7ZIP-NOTFOUND")
+      message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+    endif()
+
+  else()
+    find_program(ORTHANC_FRAMEWORK_TAR tar)
+    if (${ORTHANC_FRAMEWORK_TAR} MATCHES "ORTHANC_FRAMEWORK_TAR-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'tar' package")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework specified as a path on the filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "path")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ROOT)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ROOT must provide the path to the sources of Orthanc")
+  endif()
+  
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT})
+    message(FATAL_ERROR "Non-existing directory: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+  
+  if (NOT EXISTS ${ORTHANC_FRAMEWORK_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
+    message(FATAL_ERROR "Directory not containing the source code of Orthanc: ${ORTHANC_FRAMEWORK_ROOT}")
+  endif()
+  
+  set(ORTHANC_ROOT ${ORTHANC_FRAMEWORK_ROOT})
+endif()
+
+
+
+##
+## Case of the Orthanc framework cloned using Mercurial
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg")
+  if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+    message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+  endif()
+
+  set(ORTHANC_ROOT ${CMAKE_BINARY_DIR}/orthanc)
+
+  if (EXISTS ${ORTHANC_ROOT})
+    message("Updating the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} pull
+      WORKING_DIRECTORY ${ORTHANC_ROOT}
+      RESULT_VARIABLE Failure
+      )    
+  else()
+    message("Forking the Orthanc source repository using Mercurial")
+    execute_process(
+      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://bitbucket.org/sjodogne/orthanc"
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )    
+  endif()
+
+  if (Failure OR NOT EXISTS ${ORTHANC_ROOT})
+    message(FATAL_ERROR "Cannot fork the Orthanc repository")
+  endif()
+
+  message("Setting branch of the Orthanc repository to: ${ORTHANC_FRAMEWORK_BRANCH}")
+
+  execute_process(
+    COMMAND ${ORTHANC_FRAMEWORK_HG} update -c ${ORTHANC_FRAMEWORK_BRANCH}
+    WORKING_DIRECTORY ${ORTHANC_ROOT}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while running Mercurial")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework provided as a source archive on the
+## filesystem
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive")
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE)
+    message(FATAL_ERROR "The variable ORTHANC_FRAMEWORK_ARCHIVE must provide the path to the sources of Orthanc")
+  endif()
+endif()
+
+
+
+##
+## Case of the Orthanc framework downloaded from the Web
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+  if (DEFINED ORTHANC_FRAMEWORK_URL)
+    string(REGEX REPLACE "^.*/" "" ORTHANC_FRAMEMORK_FILENAME "${ORTHANC_FRAMEWORK_URL}")
+  else()
+    # Default case: Download from the official Web site
+    set(ORTHANC_FRAMEMORK_FILENAME Orthanc-${ORTHANC_FRAMEWORK_VERSION}.tar.gz)
+    #set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/get.php?path=/orthanc/${ORTHANC_FRAMEMORK_FILENAME}")
+    set(ORTHANC_FRAMEWORK_URL "http://www.orthanc-server.com/downloads/third-party/orthanc-framework/${ORTHANC_FRAMEMORK_FILENAME}")
+  endif()
+
+  set(ORTHANC_FRAMEWORK_ARCHIVE "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${ORTHANC_FRAMEMORK_FILENAME}")
+
+  if (NOT EXISTS "${ORTHANC_FRAMEWORK_ARCHIVE}")
+    if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+      message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+    endif()
+
+    message("Downloading: ${ORTHANC_FRAMEWORK_URL}")
+
+    file(DOWNLOAD
+      "${ORTHANC_FRAMEWORK_URL}" "${ORTHANC_FRAMEWORK_ARCHIVE}" 
+      SHOW_PROGRESS EXPECTED_MD5 "${ORTHANC_FRAMEWORK_MD5}"
+      TIMEOUT 60
+      INACTIVITY_TIMEOUT 60
+      )
+  else()
+    message("Using local copy of: ${ORTHANC_FRAMEWORK_URL}")
+  endif()  
+endif()
+
+
+
+
+##
+## Uncompressing the Orthanc framework, if it was retrieved from a
+## source archive on the filesystem, or from the official Web site
+##
+
+if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "archive" OR
+    ORTHANC_FRAMEWORK_SOURCE STREQUAL "web")
+
+  if (NOT DEFINED ORTHANC_FRAMEWORK_ARCHIVE OR
+      NOT DEFINED ORTHANC_FRAMEWORK_VERSION OR
+      NOT DEFINED ORTHANC_FRAMEWORK_MD5)
+    message(FATAL_ERROR "Internal error")
+  endif()
+
+  if (ORTHANC_FRAMEWORK_MD5 STREQUAL "")
+    message(FATAL_ERROR "Unknown release of Orthanc: ${ORTHANC_FRAMEWORK_VERSION}")
+  endif()
+
+  file(MD5 ${ORTHANC_FRAMEWORK_ARCHIVE} ActualMD5)
+
+  if (NOT "${ActualMD5}" STREQUAL "${ORTHANC_FRAMEWORK_MD5}")
+    message(FATAL_ERROR "The MD5 hash of the Orthanc archive is invalid: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+  endif()
+
+  set(ORTHANC_ROOT "${CMAKE_BINARY_DIR}/Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
+
+  if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+    if (NOT ORTHANC_FRAMEWORK_ARCHIVE MATCHES ".tar.gz$")
+      message(FATAL_ERROR "Archive should have the \".tar.gz\" extension: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+    endif()
+    
+    message("Uncompressing: ${ORTHANC_FRAMEWORK_ARCHIVE}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} e -y ${ORTHANC_FRAMEWORK_ARCHIVE}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+      
+      if (Failure)
+        message(FATAL_ERROR "Error while running the uncompression tool")
+      endif()
+
+      get_filename_component(TMP_FILENAME "${ORTHANC_FRAMEWORK_ARCHIVE}" NAME)
+      string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+
+      execute_process(
+        COMMAND ${ORTHANC_FRAMEWORK_7ZIP} x -y ${TMP_FILENAME2}
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        OUTPUT_QUIET
+        )
+
+    else()
+      execute_process(
+        COMMAND sh -c "${ORTHANC_FRAMEWORK_TAR} xfz ${ORTHANC_FRAMEWORK_ARCHIVE}"
+        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+        RESULT_VARIABLE Failure
+        )
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${ORTHANC_ROOT}")
+      message(FATAL_ERROR "The Orthanc framework was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/LinuxStandardBaseToolchain.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,66 @@
+# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON
+
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH})
+SET(LSB_CC $ENV{LSB_CC})
+SET(LSB_CXX $ENV{LSB_CXX})
+SET(LSB_TARGET_VERSION "4.0")
+
+IF ("${LSB_PATH}" STREQUAL "")
+  SET(LSB_PATH "/opt/lsb")
+ENDIF()
+
+IF (EXISTS ${LSB_PATH}/lib64)
+  SET(LSB_TARGET_PROCESSOR "x86_64")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION})
+ELSEIF (EXISTS ${LSB_PATH}/lib)
+  SET(LSB_TARGET_PROCESSOR "x86")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION})
+ELSE()
+  MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.")
+ENDIF()
+
+SET(LSB_CPPPATH ${LSB_PATH}/include)
+SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/)
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION LinuxStandardBase)
+SET(CMAKE_SYSTEM_PROCESSOR ${LSB_TARGET_PROCESSOR})
+
+# which compilers to use for C and C++
+SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc)
+CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
+
+SET(CMAKE_CROSSCOMPILING OFF)
+
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include" CACHE INTERNAL "" FORCE)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward" CACHE INTERNAL "" FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+
+if (NOT "${LSB_CXX}" STREQUAL "")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+endif()
+
+if (NOT "${LSB_CC}" STREQUAL "")
+  SET(CMAKE_C_FLAGS "${CMAKE_CC_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGW-W64-Toolchain32.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGW-W64-Toolchain64.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/MinGWToolchain.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,20 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTACK_SIZE_PARAM_IS_A_RESERVATION=0x10000" CACHE INTERNAL "" FORCE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-0.9.5/orthanc/OrthancCDatabasePlugin.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,808 @@
+/**
+ * @ingroup CInterface
+ **/
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+#include "OrthancCPlugin.h"
+
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+  /**
+   * Opaque structure that represents the context of a custom database engine.
+   * @ingroup Callbacks
+   **/
+  typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext;
+
+
+/*<! @cond Doxygen_Suppress */
+  typedef enum
+  {
+    _OrthancPluginDatabaseAnswerType_None = 0,
+
+    /* Events */
+    _OrthancPluginDatabaseAnswerType_DeletedAttachment = 1,
+    _OrthancPluginDatabaseAnswerType_DeletedResource = 2,
+    _OrthancPluginDatabaseAnswerType_RemainingAncestor = 3,
+
+    /* Return value */
+    _OrthancPluginDatabaseAnswerType_Attachment = 10,
+    _OrthancPluginDatabaseAnswerType_Change = 11,
+    _OrthancPluginDatabaseAnswerType_DicomTag = 12,
+    _OrthancPluginDatabaseAnswerType_ExportedResource = 13,
+    _OrthancPluginDatabaseAnswerType_Int32 = 14,
+    _OrthancPluginDatabaseAnswerType_Int64 = 15,
+    _OrthancPluginDatabaseAnswerType_Resource = 16,
+    _OrthancPluginDatabaseAnswerType_String = 17,
+
+    _OrthancPluginDatabaseAnswerType_INTERNAL = 0x7fffffff
+  } _OrthancPluginDatabaseAnswerType;
+
+
+  typedef struct
+  {
+    const char* uuid;
+    int32_t     contentType;
+    uint64_t    uncompressedSize;
+    const char* uncompressedHash;
+    int32_t     compressionType;
+    uint64_t    compressedSize;
+    const char* compressedHash;
+  } OrthancPluginAttachment;
+
+  typedef struct
+  {
+    uint16_t     group;
+    uint16_t     element;
+    const char*  value;
+  } OrthancPluginDicomTag;
+
+  typedef struct
+  {
+    int64_t                    seq;
+    int32_t                    changeType;
+    OrthancPluginResourceType  resourceType;
+    const char*                publicId;
+    const char*                date;
+  } OrthancPluginChange;
+
+  typedef struct
+  {
+    int64_t                    seq;
+    OrthancPluginResourceType  resourceType;
+    const char*                publicId;
+    const char*                modality;
+    const char*                date;
+    const char*                patientId;
+    const char*                studyInstanceUid;
+    const char*                seriesInstanceUid;
+    const char*                sopInstanceUid;
+  } OrthancPluginExportedResource;
+
+
+  typedef struct
+  {
+    OrthancPluginDatabaseContext* database;
+    _OrthancPluginDatabaseAnswerType  type;
+    int32_t      valueInt32;
+    uint32_t     valueUint32;
+    int64_t      valueInt64;
+    const char  *valueString;
+    const void  *valueGeneric;
+  } _OrthancPluginDatabaseAnswer;
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerString(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_String;
+    params.valueString = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginChange*     change)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Change;
+    params.valueUint32 = 0;
+    params.valueGeneric = change;
+
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Change;
+    params.valueUint32 = 1;
+    params.valueGeneric = NULL;
+
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int32_t                        value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Int32;
+    params.valueInt32 = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int64_t                        value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Int64;
+    params.valueInt64 = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource(
+    OrthancPluginContext*                 context,
+    OrthancPluginDatabaseContext*         database,
+    const OrthancPluginExportedResource*  exported)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_ExportedResource;
+    params.valueUint32 = 0;
+    params.valueGeneric = exported;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone(
+    OrthancPluginContext*                 context,
+    OrthancPluginDatabaseContext*         database)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_ExportedResource;
+    params.valueUint32 = 1;
+    params.valueGeneric = NULL;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginDicomTag*   tag)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DicomTag;
+    params.valueGeneric = tag;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginAttachment* attachment)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Attachment;
+    params.valueGeneric = attachment;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int64_t                        id,
+    OrthancPluginResourceType      resourceType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Resource;
+    params.valueInt64 = id;
+    params.valueInt32 = (int32_t) resourceType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginAttachment* attachment)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment;
+    params.valueGeneric = attachment;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    publicId,
+    OrthancPluginResourceType      resourceType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DeletedResource;
+    params.valueString = publicId;
+    params.valueInt32 = (int32_t) resourceType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    ancestorId,
+    OrthancPluginResourceType      ancestorType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor;
+    params.valueString = ancestorId;
+    params.valueInt32 = (int32_t) ancestorType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode  (*addAttachment) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginAttachment* attachment);
+                             
+    OrthancPluginErrorCode  (*attachChild) (
+      /* inputs */
+      void* payload,
+      int64_t parent,
+      int64_t child);
+                             
+    OrthancPluginErrorCode  (*clearChanges) (
+      /* inputs */
+      void* payload);
+                             
+    OrthancPluginErrorCode  (*clearExportedResources) (
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*createResource) (
+      /* outputs */
+      int64_t* id, 
+      /* inputs */
+      void* payload,
+      const char* publicId,
+      OrthancPluginResourceType resourceType);           
+                   
+    OrthancPluginErrorCode  (*deleteAttachment) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t contentType);
+   
+    OrthancPluginErrorCode  (*deleteMetadata) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadataType);
+   
+    OrthancPluginErrorCode  (*deleteResource) (
+      /* inputs */
+      void* payload,
+      int64_t id);    
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*getAllPublicIds) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerChange() and
+     * OrthancPluginDatabaseAnswerChangesDone() */
+    OrthancPluginErrorCode  (*getChanges) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t since,
+      uint32_t maxResult);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*getChildrenInternalId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*getChildrenPublicId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and
+     * OrthancPluginDatabaseAnswerExportedResourcesDone() */
+    OrthancPluginErrorCode  (*getExportedResources) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t  since,
+      uint32_t  maxResult);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerChange() */
+    OrthancPluginErrorCode  (*getLastChange) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+
+    /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */
+    OrthancPluginErrorCode  (*getLastExportedResource) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */
+    OrthancPluginErrorCode  (*getMainDicomTags) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*getPublicId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    OrthancPluginErrorCode  (*getResourceCount) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType  resourceType);
+                   
+    OrthancPluginErrorCode  (*getResourceType) (
+      /* outputs */
+      OrthancPluginResourceType* resourceType,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    OrthancPluginErrorCode  (*getTotalCompressedSize) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload);
+                   
+    OrthancPluginErrorCode  (*getTotalUncompressedSize) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload);
+                   
+    OrthancPluginErrorCode  (*isExistingResource) (
+      /* outputs */
+      int32_t* existing,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    OrthancPluginErrorCode  (*isProtectedPatient) (
+      /* outputs */
+      int32_t* isProtected,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt32() */
+    OrthancPluginErrorCode  (*listAvailableMetadata) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerInt32() */
+    OrthancPluginErrorCode  (*listAvailableAttachments) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    OrthancPluginErrorCode  (*logChange) (
+      /* inputs */
+      void* payload,
+      const OrthancPluginChange* change);
+                   
+    OrthancPluginErrorCode  (*logExportedResource) (
+      /* inputs */
+      void* payload,
+      const OrthancPluginExportedResource* exported);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerAttachment() */
+    OrthancPluginErrorCode  (*lookupAttachment) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t contentType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*lookupGlobalProperty) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int32_t property);
+
+    /* Use "OrthancPluginDatabaseExtensions::lookupIdentifier3" 
+       instead of this function as of Orthanc 0.9.5 (db v6), can be set to NULL.
+       Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*lookupIdentifier) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const OrthancPluginDicomTag* tag);
+
+    /* Unused starting with Orthanc 0.9.5 (db v6), can be set to NULL.
+       Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*lookupIdentifier2) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const char* value);
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*lookupMetadata) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadata);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*lookupParent) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerResource() */
+    OrthancPluginErrorCode  (*lookupResource) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const char* publicId);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*selectPatientToRecycle) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*selectPatientToRecycle2) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t patientIdToAvoid);
+
+    OrthancPluginErrorCode  (*setGlobalProperty) (
+      /* inputs */
+      void* payload,
+      int32_t property,
+      const char* value);
+
+    OrthancPluginErrorCode  (*setMainDicomTag) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginDicomTag* tag);
+
+    OrthancPluginErrorCode  (*setIdentifierTag) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginDicomTag* tag);
+
+    OrthancPluginErrorCode  (*setMetadata) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadata,
+      const char* value);
+
+    OrthancPluginErrorCode  (*setProtectedPatient) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t isProtected);
+
+    OrthancPluginErrorCode  (*startTransaction) (
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*rollbackTransaction) (
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*commitTransaction) (
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*open) (
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*close) (
+      /* inputs */
+      void* payload);
+
+  } OrthancPluginDatabaseBackend;
+
+
+  typedef struct
+  {
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    OrthancPluginErrorCode  (*getAllPublicIdsWithLimit) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType,
+      uint64_t since,
+      uint64_t limit);
+
+    OrthancPluginErrorCode  (*getDatabaseVersion) (
+      /* outputs */
+      uint32_t* version,
+      /* inputs */
+      void* payload);
+
+    OrthancPluginErrorCode  (*upgradeDatabase) (
+      /* inputs */
+      void* payload,
+      uint32_t targetVersion,
+      OrthancPluginStorageArea* storageArea);
+ 
+    OrthancPluginErrorCode  (*clearMainDicomTags) (
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*getAllInternalIds) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*lookupIdentifier3) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType,
+      const OrthancPluginDicomTag* tag,
+      OrthancPluginIdentifierConstraint constraint);
+   } OrthancPluginDatabaseExtensions;
+
+/*<! @endcond */
+
+
+  typedef struct
+  {
+    OrthancPluginDatabaseContext**       result;
+    const OrthancPluginDatabaseBackend*  backend;
+    void*                                payload;
+  } _OrthancPluginRegisterDatabaseBackend;
+
+  /**
+   * Register a custom database back-end.
+   *
+   * Instead of manually filling the OrthancPluginDatabaseBackend
+   * structure, you should instead implement a concrete C++ class
+   * deriving from ::OrthancPlugins::IDatabaseBackend, and register it
+   * using ::OrthancPlugins::DatabaseBackendAdapter::Register().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param backend The callbacks of the custom database engine.
+   * @param payload Pointer containing private information for the database engine.
+   * @return The context of the database engine (it must not be manually freed).
+   * @ingroup Callbacks
+   * @deprecated
+   * @see OrthancPluginRegisterDatabaseBackendV2
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackend(
+    OrthancPluginContext*                context,
+    const OrthancPluginDatabaseBackend*  backend,
+    void*                                payload)
+  {
+    OrthancPluginDatabaseContext* result = NULL;
+    _OrthancPluginRegisterDatabaseBackend params;
+
+    if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType))
+    {
+      return NULL;
+    }
+
+    memset(&params, 0, sizeof(params));
+    params.backend = backend;
+    params.result = &result;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, &params) ||
+        result == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDatabaseContext**          result;
+    const OrthancPluginDatabaseBackend*     backend;
+    void*                                   payload;
+    const OrthancPluginDatabaseExtensions*  extensions;
+    uint32_t                                extensionsSize;
+  } _OrthancPluginRegisterDatabaseBackendV2;
+
+
+  /**
+   * Register a custom database back-end.
+   *
+   * Instead of manually filling the OrthancPluginDatabaseBackendV2
+   * structure, you should instead implement a concrete C++ class
+   * deriving from ::OrthancPlugins::IDatabaseBackend, and register it
+   * using ::OrthancPlugins::DatabaseBackendAdapter::Register().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param backend The callbacks of the custom database engine.
+   * @param payload Pointer containing private information for the database engine.
+   * @param extensions Extensions to the base database SDK that was shipped until Orthanc 0.9.3.
+   * @return The context of the database engine (it must not be manually freed).
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackendV2(
+    OrthancPluginContext*                   context,
+    const OrthancPluginDatabaseBackend*     backend,
+    const OrthancPluginDatabaseExtensions*  extensions,
+    void*                                   payload)
+  {
+    OrthancPluginDatabaseContext* result = NULL;
+    _OrthancPluginRegisterDatabaseBackendV2 params;
+
+    if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType))
+    {
+      return NULL;
+    }
+
+    memset(&params, 0, sizeof(params));
+    params.backend = backend;
+    params.result = &result;
+    params.payload = payload;
+    params.extensions = extensions;
+    params.extensionsSize = sizeof(OrthancPluginDatabaseExtensions);
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV2, &params) ||
+        result == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-0.9.5/orthanc/OrthancCPlugin.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,4686 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup Worklists Worklists
+ * @brief Functions to register and manage worklists.
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     9
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  5
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+/**
+ * For Microsoft Visual Studio, a compatibility "stdint.h" can be
+ * downloaded at the following URL:
+ * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h
+ **/
+#include <stdint.h>
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various error codes that can be returned by the Orthanc core.
+   **/
+  typedef enum
+  {
+    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
+    OrthancPluginErrorCode_Success = 0    /*!< Success */,
+    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
+    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
+    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
+    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
+    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
+
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,
+    _OrthancPluginService_DatabaseAnswer = 5001,
+    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
+
+    /* Primitives for handling images */
+    _OrthancPluginService_GetImagePixelFormat = 6000,
+    _OrthancPluginService_GetImageWidth = 6001,
+    _OrthancPluginService_GetImageHeight = 6002,
+    _OrthancPluginService_GetImagePitch = 6003,
+    _OrthancPluginService_GetImageBuffer = 6004,
+    _OrthancPluginService_UncompressImage = 6005,
+    _OrthancPluginService_FreeImage = 6006,
+    _OrthancPluginService_CompressImage = 6007,
+    _OrthancPluginService_ConvertPixelFormat = 6008,
+    _OrthancPluginService_GetFontsCount = 6009,
+    _OrthancPluginService_GetFontInfo = 6010,
+    _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query.
+   * @ingroup Worklists
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       remoteAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              subType,
+    const char*              contentType)
+  {
+    _OrthancPluginStartMultipartAnswer params;
+    params.output = output;
+    params.subType = subType;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*    target;
+    const void*                   source;
+    uint32_t                      size;
+    OrthancPluginCompressionType  compression;
+    uint8_t                       uncompress;
+  } _OrthancPluginBufferCompression;
+
+
+  /**
+   * @brief Compress or decompress a buffer.
+   *
+   * This function compresses or decompresses a buffer, using the
+   * version of the zlib library that is used by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param source The source buffer.
+   * @param size The size in bytes of the source buffer.
+   * @param compression The compression algorithm.
+   * @param uncompress If set to "0", the buffer must be compressed. 
+   * If set to "1", the buffer must be uncompressed.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    const void*                   source,
+    uint32_t                      size,
+    OrthancPluginCompressionType  compression,
+    uint8_t                       uncompress)
+  {
+    _OrthancPluginBufferCompression params;
+    params.target = target;
+    params.source = source;
+    params.size = size;
+    params.compression = compression;
+    params.uncompress = uncompress;
+
+    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 path;
+  } _OrthancPluginReadFile;
+
+  /**
+   * @brief Read a file.
+   * 
+   * Read the content of a file on the filesystem, and returns it into
+   * a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param path The path of the file to be read.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 path)
+  {
+    _OrthancPluginReadFile params;
+    params.target = target;
+    params.path = path;
+    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char*  path;
+    const void*  data;
+    uint32_t     size;
+  } _OrthancPluginWriteFile;
+
+  /**
+   * @brief Write a file.
+   * 
+   * Write the content of a memory buffer to the filesystem.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path The path of the file to be written.
+   * @param data The content of the memory buffer.
+   * @param size The size of the memory buffer.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
+    OrthancPluginContext*  context,
+    const char*            path,
+    const void*            data,
+    uint32_t               size)
+  {
+    _OrthancPluginWriteFile params;
+    params.path = path;
+    params.data = data;
+    params.size = size;
+    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char**            target;
+    OrthancPluginErrorCode  error;
+  } _OrthancPluginGetErrorDescription;
+
+  /**
+   * @brief Get the description of a given error code.
+   *
+   * This function returns the description of a given error code.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param error The error code of interest.
+   * @return The error description. This is a statically-allocated
+   * string, do not free it.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
+    OrthancPluginContext*    context,
+    OrthancPluginErrorCode   error)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetErrorDescription params;
+    params.target = &result;
+    params.error = error;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
+        result == NULL)
+    {
+      return "Unknown error code";
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+    const char*              body;
+    uint32_t                 bodySize;
+  } _OrthancPluginSendHttpStatus;
+
+  /**
+   * @brief Send a HTTP status, with a custom body.
+   *
+   * This function answers to a HTTP request by sending a HTTP status
+   * code (such as "400 - Bad Request"), together with a body
+   * describing the error. The body will only be returned if the
+   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
+   * 
+   * Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @param body The body of the answer.
+   * @param bodySize The size of the body.
+   * @see OrthancPluginSendHttpStatusCode()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status,
+    const char*              body,
+    uint32_t                 bodySize)
+  {
+    _OrthancPluginSendHttpStatus params;
+    params.output = output;
+    params.status = status;
+    params.body = body;
+    params.bodySize = bodySize;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const OrthancPluginImage*  image;
+    uint32_t*                  resultUint32;
+    OrthancPluginPixelFormat*  resultPixelFormat;
+    void**                     resultBuffer;
+  } _OrthancPluginGetImageInfo;
+
+
+  /**
+   * @brief Return the pixel format of an image.
+   *
+   * This function returns the type of memory layout for the pixels of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pixel format.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    OrthancPluginPixelFormat target;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultPixelFormat = &target;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginPixelFormat_Unknown;
+    }
+    else
+    {
+      return (OrthancPluginPixelFormat) target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the width of an image.
+   *
+   * This function returns the width of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The width.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t width;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &width;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return width;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the height of an image.
+   *
+   * This function returns the height of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The height.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t height;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &height;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return height;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the pitch of an image.
+   *
+   * This function returns the pitch of the given image. The pitch is
+   * defined as the number of bytes between 2 successive lines of the
+   * image in the memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pitch.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t pitch;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &pitch;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return pitch;
+    }
+  }
+
+
+
+  /**
+   * @brief Return a pointer to the content of an image.
+   *
+   * This function returns a pointer to the memory buffer that
+   * contains the pixels of the image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pointer.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    void* target = NULL;
+
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.resultBuffer = &target;
+    params.image = image;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const void*                data;
+    uint32_t                   size;
+    OrthancPluginImageFormat   format;
+  } _OrthancPluginUncompressImage;
+
+
+  /**
+   * @brief Decode a compressed image.
+   *
+   * This function decodes a compressed image from a memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param data Pointer to a memory buffer containing the compressed image.
+   * @param size Size of the memory buffer containing the compressed image.
+   * @param format The file format of the compressed image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
+    OrthancPluginContext*      context,
+    const void*                data,
+    uint32_t                   size,
+    OrthancPluginImageFormat   format)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginUncompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.data = data;
+    params.size = size;
+    params.format = format;
+
+    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+  } _OrthancPluginFreeImage;
+
+  /**
+   * @brief Free an image.
+   *
+   * This function frees an image that was decoded with OrthancPluginUncompressImage().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
+    OrthancPluginContext* context, 
+    OrthancPluginImage*   image)
+  {
+    _OrthancPluginFreeImage params;
+    params.image = image;
+
+    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer* target;
+    OrthancPluginImageFormat   imageFormat;
+    OrthancPluginPixelFormat   pixelFormat;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    const void*                buffer;
+    uint8_t                    quality;
+  } _OrthancPluginCompressImage;
+
+
+  /**
+   * @brief Encode a PNG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the PNG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCompressAndAnswerPngImage()
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* Unused for PNG */
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+  /**
+   * @brief Encode a JPEG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the JPEG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer,
+    uint8_t                       quality)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+
+  /**
+   * @brief Answer to a REST request with a JPEG image.
+   *
+   * This function answers to a REST request with a JPEG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a JPEG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer,
+    uint8_t                   quality)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    const char*                 username;
+    const char*                 password;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginCallHttpClient;
+
+
+  /**
+   * @brief Issue a HTTP GET call.
+   * 
+   * Make a HTTP GET call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiGet() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Get;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP POST call.
+   * 
+   * Make a HTTP POST call to the given URL. The result to the query
+   * is stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPost() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Post;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP PUT call.
+   * 
+   * Make a HTTP PUT call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPut() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Put;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP DELETE call.
+   * 
+   * Make a HTTP DELETE call to the given URL. Favor
+   * OrthancPluginRestApiDelete() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
+    OrthancPluginContext*       context,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.method = OrthancPluginHttpMethod_Delete;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const OrthancPluginImage*  source;
+    OrthancPluginPixelFormat   targetFormat;
+  } _OrthancPluginConvertPixelFormat;
+
+
+  /**
+   * @brief Change the pixel format of an image.
+   *
+   * This function creates a new image, changing the memory layout of the pixels.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param source The source image.
+   * @param targetFormat The target pixel format.
+   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  source,
+    OrthancPluginPixelFormat   targetFormat)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginConvertPixelFormat params;
+    params.target = &target;
+    params.source = source;
+    params.targetFormat = targetFormat;
+
+    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the number of available fonts.
+   *
+   * This function returns the number of fonts that are built in the
+   * Orthanc core. These fonts can be used to draw texts on images
+   * through OrthancPluginDrawText().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of fonts.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    uint32_t      fontIndex; /* in */
+    const char**  name; /* out */
+    uint32_t*     size; /* out */
+  } _OrthancPluginGetFontInfo;
+
+  /**
+   * @brief Return the name of a font.
+   *
+   * This function returns the name of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font name. This is a statically-allocated string, do not free it.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.name = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the size of a font.
+   *
+   * This function returns the size of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font size.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    uint32_t result;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.size = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+    uint32_t              fontIndex;
+    const char*           utf8Text;
+    int32_t               x;
+    int32_t               y;
+    uint8_t               r;
+    uint8_t               g;
+    uint8_t               b;
+  } _OrthancPluginDrawText;
+
+
+  /**
+   * @brief Draw text on an image.
+   *
+   * This function draws some text on some image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image upon which to draw the text.
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
+   * @param x The X position of the text over the image.
+   * @param y The Y position of the text over the image.
+   * @param r The value of the red color channel of the text.
+   * @param g The value of the green color channel of the text.
+   * @param b The value of the blue color channel of the text.
+   * @return 0 if success, other value if error.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
+    OrthancPluginContext*  context,
+    OrthancPluginImage*    image,
+    uint32_t               fontIndex,
+    const char*            utf8Text,
+    int32_t                x,
+    int32_t                y,
+    uint8_t                r,
+    uint8_t                g,
+    uint8_t                b)
+  {
+    _OrthancPluginDrawText params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.fontIndex = fontIndex;
+    params.utf8Text = utf8Text;
+    params.x = x;
+    params.y = y;
+    params.r = r;
+    params.g = g;
+    params.b = b;
+
+    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = &target;
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new tag in the dictionary of DICOM tags
+   * that are known to Orthanc. This function should be used in the
+   * OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end
+   * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A
+   * database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/PostgreSQL/PrepareCMakeConfigurationFile.py	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+
+import re
+import sys
+
+if len(sys.argv) != 3:
+    raise Exception('Bad number of arguments')
+
+r = re.compile(r'^#undef ([A-Z0-9_]+)$')
+
+with open(sys.argv[1], 'r') as f:
+    with open(sys.argv[2], 'w') as g:
+        for l in f.readlines():
+            m = r.match(l)
+            if m != None:
+                s = m.group(1)
+                g.write('#cmakedefine %s @%s@\n' % (s, s))
+            else:
+                g.write(l)
+                
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/PostgreSQL/c_flexmember.c	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+struct s { int n; double d[]; };
+
+int main ()
+{
+  int m = getchar ();
+  struct s *p = malloc (offsetof (struct s, d)
+                        + m * sizeof (double));
+  p->d[0] = 0.0;
+  return p->d != (double *) NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/PostgreSQL/func_accept_args.cmake	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,59 @@
+# This mimics ac_func_accept_argtypes.m4
+
+check_include_files(sys/types.h HAVE_SYS_TYPES_H)
+check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
+
+if(HAVE_SYS_TYPES_H)
+  set(INCLUDE_SYS_TYPES_H "#include <sys/types.h>")
+endif(HAVE_SYS_TYPES_H)
+
+if(HAVE_SYS_SOCKET_H)
+  set(INCLUDE_SYS_SOCKET_H "#include <sys/socket.h>")
+endif(HAVE_SYS_SOCKET_H)
+
+message(STATUS "Looking for accept function args")
+set(CMAKE_REQUIRED_QUIET 1)
+foreach(ac_cv_func_accept_return "int" "unsigned int PASCAL" "SOCKET WSAAPI")
+  foreach(ac_cv_func_accept_arg1 "int" "unsigned int" "SOCKET")
+    foreach(ac_cv_func_accept_arg2 "struct sockaddr *" "const struct sockaddr *" "void *")
+      foreach(ac_cv_func_accept_arg3 "int" "size_t" "socklen_t" "unsigned int" "void")
+	unset(AC_FUNC_ACCEPT CACHE)
+	CHECK_C_SOURCE_COMPILES("
+${INCLUDE_SYS_TYPES_H}
+${INCLUDE_SYS_SOCKET_H}
+extern ${ac_cv_func_accept_return} accept (${ac_cv_func_accept_arg1}, ${ac_cv_func_accept_arg2}, ${ac_cv_func_accept_arg3} *);
+int main(void)
+{
+  return 0;
+}
+				" AC_FUNC_ACCEPT)
+	if(AC_FUNC_ACCEPT)
+	  set(ACCEPT_TYPE_RETURN ${ac_cv_func_accept_return})
+	  set(ACCEPT_TYPE_ARG1 ${ac_cv_func_accept_arg1})
+	  set(ACCEPT_TYPE_ARG2 ${ac_cv_func_accept_arg2})
+	  set(ACCEPT_TYPE_ARG3 ${ac_cv_func_accept_arg3})
+	  break()
+	endif(AC_FUNC_ACCEPT)
+      endforeach(ac_cv_func_accept_arg3)
+      if(AC_FUNC_ACCEPT)
+	break()
+      endif(AC_FUNC_ACCEPT)
+    endforeach(ac_cv_func_accept_arg2)
+    if(AC_FUNC_ACCEPT)
+      break()
+    endif(AC_FUNC_ACCEPT)
+  endforeach(ac_cv_func_accept_arg1)
+  if(AC_FUNC_ACCEPT)
+    break()
+  endif(AC_FUNC_ACCEPT)
+endforeach(ac_cv_func_accept_return)
+unset(CMAKE_REQUIRED_QUIET)
+
+if(NOT AC_FUNC_ACCEPT)
+  message(ERROR "could not determine argument types")
+endif(NOT AC_FUNC_ACCEPT)
+if(ac_cv_func_accept_arg3 EQUAL "void")
+  set(ac_cv_func_accept_arg3 "int")
+endif(ac_cv_func_accept_arg3 EQUAL "void")
+
+message(STATUS "Looking for accept function args - found ${ACCEPT_TYPE_RETURN}, ${ACCEPT_TYPE_ARG1}, ${ACCEPT_TYPE_ARG2}, ${ACCEPT_TYPE_ARG3} *")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/PostgreSQL/pg_config_ext.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,8 @@
+/*
+ * src/include/pg_config_ext.h.in.  This is generated manually, not by
+ * autoheader, since we want to limit which symbols get defined here.
+ */
+
+/* Define to the name of a signed 64-bit integer type. */
+#include <stdint.h>
+#define PG_INT64_TYPE int64_t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/PostgreSQL/printf_archetype.c	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,10 @@
+extern int
+pgac_write(int ignore, const char *fmt,...)
+__attribute__((format(gnu_printf, 2, 3)));
+int
+main ()
+{
+
+  ;
+  return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '0.9.5'
+REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
+
+FILES = [
+    'DownloadOrthancFramework.cmake',
+    'LinuxStandardBaseToolchain.cmake',
+    'MinGW-W64-Toolchain32.cmake',
+    'MinGW-W64-Toolchain64.cmake',
+    'MinGWToolchain.cmake',
+]
+
+SDK = [
+    'orthanc/OrthancCPlugin.h',
+    'orthanc/OrthancCDatabasePlugin.h',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    with open(target, 'w') as f:
+        f.write(urllib2.urlopen(url).read())
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default',
+                      os.path.join('Resources', f),
+                      f ])
+
+for f in SDK:
+    commands.append([
+        'Orthanc-%s' % PLUGIN_SDK_VERSION, 
+        'Plugins/Include/%s' % f,
+        'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) 
+    ])
+
+
+pool = multiprocessing.Pool(10)  # simultaneous downloads
+pool.map(Download, commands)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/CMakeLists.txt	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,68 @@
+cmake_minimum_required(VERSION 2.8)
+project(OrthancSQLite)
+
+set(ORTHANC_PLUGIN_VERSION "mainline")
+
+if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
+  set(ORTHANC_FRAMEWORK_VERSION "mainline")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
+  set(ORTHANC_FRAMEWORK_BRANCH "jobs")  # TODO remove this
+else()
+  set(ORTHANC_FRAMEWORK_VERSION "1.3.2")
+  set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
+endif()
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginParameters.cmake)
+
+set(ENABLE_SQLITE_BACKEND ON)
+  
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DatabasesPluginConfiguration.cmake)
+
+EmbedResources(
+  SQLITE_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
+  )
+
+add_library(OrthancSQLiteIndex SHARED
+  Plugins/SQLiteIndex.cpp
+  Plugins/IndexPlugin.cpp
+  ${DATABASES_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+message("Setting the version of the libraries to ${ORTHANC_PLUGIN_VERSION}")
+
+add_definitions(
+  -DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}"
+  -DHAS_ORTHANC_EXCEPTION=1
+  )
+
+#set_target_properties(OrthancSQLiteStorage PROPERTIES 
+#  VERSION ${ORTHANC_PLUGIN_VERSION} 
+#  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+#  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+#  )
+
+set_target_properties(OrthancSQLiteIndex PROPERTIES 
+  VERSION ${ORTHANC_PLUGIN_VERSION} 
+  SOVERSION ${ORTHANC_PLUGIN_VERSION}
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  )
+
+install(
+  TARGETS OrthancSQLiteIndex  # OrthancSQLiteStorage  TODO
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
+
+add_executable(UnitTests
+  Plugins/SQLiteIndex.cpp
+  UnitTests/UnitTestsMain.cpp
+  ${DATABASES_SOURCES}
+  ${GOOGLE_TEST_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+  )
+
+target_link_libraries(UnitTests ${GOOGLE_TEST_LIBRARIES})
+set_target_properties(UnitTests PROPERTIES
+  COMPILE_FLAGS -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/NEWS	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,4 @@
+Pending changes in the mainline
+===============================
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/Plugins/IndexPlugin.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteIndex.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Logging.h>
+
+static OrthancPluginContext* context_ = NULL;
+static std::auto_ptr<OrthancDatabases::SQLiteIndex> backend_;
+
+
+
+static bool DisplayPerformanceWarning()
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context_, "Performance warning in SQLite index: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    Orthanc::Logging::Initialize(context);
+
+    context_ = context;
+    assert(DisplayPerformanceWarning());
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context_, "Stores the Orthanc index into a SQLite database.");
+
+#if 0
+    OrthancPlugins::OrthancConfiguration configuration(context);
+
+    if (!configuration.IsSection("SQLite"))
+    {
+      LOG(WARNING) << "No available configuration for the SQLite index plugin";
+      return 0;
+    }
+
+    OrthancPlugins::OrthancConfiguration sqlite;
+    configuration.GetSection(sqlite, "SQLite");
+
+    bool enable;
+    if (!sqlite.LookupBooleanValue(enable, "EnableIndex") ||
+        !enable)
+    {
+      LOG(WARNING) << "The SQLite index is currently disabled, set \"EnableIndex\" "
+                   << "to \"true\" in the \"SQLite\" section of the configuration file of Orthanc";
+      return 0;
+    }
+#endif
+
+    try
+    {
+      /* Create the database back-end */
+      backend_.reset(new OrthancDatabases::SQLiteIndex("index.db"));  // TODO parameter
+
+      /* Register the SQLite index into Orthanc */
+      OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_);
+    }
+    catch (std::runtime_error& e)
+    {
+      OrthancPluginLogError(context_, e.what());
+      return -1;
+    }
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "SQLite index is finalizing");
+    backend_.reset(NULL);
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "sqlite-index";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/Plugins/PrepareIndex.sql	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,156 @@
+CREATE TABLE GlobalProperties(
+       property INTEGER PRIMARY KEY,
+       value TEXT
+       );
+
+CREATE TABLE Resources(
+       internalId INTEGER PRIMARY KEY AUTOINCREMENT,
+       resourceType INTEGER,
+       publicId TEXT,
+       parentId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE TABLE MainDicomTags(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, tagGroup, tagElement)
+       );
+
+CREATE TABLE DicomIdentifiers(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, tagGroup, tagElement)
+       );
+
+CREATE TABLE Metadata(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       type INTEGER,
+       value TEXT,
+       PRIMARY KEY(id, type)
+       );
+
+CREATE TABLE AttachedFiles(
+       id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       fileType INTEGER,
+       uuid TEXT,
+       compressedSize INTEGER,
+       uncompressedSize INTEGER,
+       compressionType INTEGER,
+       uncompressedHash TEXT,
+       compressedHash TEXT,
+       PRIMARY KEY(id, fileType)
+       );              
+
+CREATE TABLE Changes(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       changeType INTEGER,
+       internalId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,
+       resourceType INTEGER,
+       date TEXT
+       );
+
+CREATE TABLE ExportedResources(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       resourceType INTEGER,
+       publicId TEXT,
+       remoteModality TEXT,
+       patientId TEXT,
+       studyInstanceUid TEXT,
+       seriesInstanceUid TEXT,
+       sopInstanceUid TEXT,
+       date TEXT
+       ); 
+
+CREATE TABLE PatientRecyclingOrder(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       patientId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
+CREATE INDEX ChildrenIndex ON Resources(parentId);
+CREATE INDEX PublicIndex ON Resources(publicId);
+CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
+
+CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id);
+-- The 2 following indexes were removed in Orthanc 0.8.5 (database v5), to speed up
+-- CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement);
+-- CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE BINARY);
+
+-- The 3 following indexes were added in Orthanc 0.8.5 (database v5)
+CREATE INDEX DicomIdentifiersIndex1 ON DicomIdentifiers(id);
+CREATE INDEX DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement);
+CREATE INDEX DicomIdentifiersIndexValues ON DicomIdentifiers(value COLLATE BINARY);
+
+CREATE INDEX ChangesIndex ON Changes(internalId);
+
+
+
+-- New tables wrt. Orthanc core
+CREATE TABLE DeletedFiles(
+       uuid TEXT NOT NULL,        -- 0
+       fileType INTEGER,          -- 1
+       compressedSize INTEGER,    -- 2
+       uncompressedSize INTEGER,  -- 3
+       compressionType INTEGER,   -- 4
+       uncompressedHash TEXT,     -- 5
+       compressedHash TEXT        -- 6
+       );
+
+CREATE TABLE RemainingAncestor(
+       resourceType INTEGER NOT NULL,
+       publicId TEXT NOT NULL
+       );
+
+CREATE TABLE DeletedResources(
+       resourceType INTEGER NOT NULL,
+       publicId TEXT NOT NULL
+       );
+-- End of differences
+
+
+
+CREATE TRIGGER AttachedFileDeleted
+AFTER DELETE ON AttachedFiles
+BEGIN
+   INSERT INTO DeletedFiles VALUES(old.uuid, old.filetype, old.compressedSize,
+                                   old.uncompressedSize, old.compressionType,
+                                   old.uncompressedHash, old.compressedHash);
+END;
+
+
+CREATE TRIGGER ResourceDeleted
+AFTER DELETE ON Resources
+BEGIN
+   INSERT INTO DeletedResources VALUES(old.resourceType, old.publicId);
+END;
+
+
+-- Delete a parent resource when its unique child gets deleted 
+CREATE TRIGGER ResourceDeletedParentCleaning
+AFTER DELETE ON Resources
+FOR EACH ROW WHEN NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId)
+BEGIN
+  DELETE FROM Resources WHERE internalId = old.parentId;
+END;
+
+-- Signal that the deleted resource has a remaining parent, if the
+-- deleted resource has a sibling resource
+CREATE TRIGGER ResourceRemainingAncestorFound
+AFTER DELETE ON Resources
+FOR EACH ROW WHEN EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId)
+BEGIN
+   INSERT INTO RemainingAncestor(resourceType, publicId)
+          SELECT resourceType, publicId FROM Resources WHERE internalId = old.parentId;
+END;
+
+
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW WHEN new.resourceType = 0  -- The "0" corresponds to "OrthancPluginResourceType_Patient"
+BEGIN
+  INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId);
+END;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/Plugins/SQLiteIndex.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,176 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SQLiteIndex.h"
+
+#include "../../Framework/Plugins/GlobalProperties.h"
+#include "../../Framework/SQLite/SQLiteDatabase.h"
+#include "../../Framework/SQLite/SQLiteTransaction.h"
+
+#include <EmbeddedResources.h>  // Auto-generated file
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancDatabases
+{
+  IDatabase* SQLiteIndex::OpenInternal()
+  {
+    uint32_t expectedVersion = 6;
+    if (context_)
+    {
+      expectedVersion = OrthancPluginGetExpectedDatabaseVersion(context_);
+    }
+    else
+    {
+      // This case only occurs during unit testing
+      expectedVersion = 6;
+    }
+
+    // Check the expected version of the database
+    if (expectedVersion != 6)
+    {
+      LOG(ERROR) << "This database plugin is incompatible with your version of Orthanc "
+                 << "expecting the DB schema version " << expectedVersion 
+                 << ", but this plugin is only compatible with version 6";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+
+    std::auto_ptr<SQLiteDatabase> db(new SQLiteDatabase);
+
+    if (path_.empty())
+    {
+      db->OpenInMemory();
+    }
+    else
+    {
+      db->Open(path_);
+    }
+
+    {
+      SQLiteTransaction t(*db);
+
+      if (!db->DoesTableExist("Resources"))
+      {
+        std::string query;
+
+        Orthanc::EmbeddedResources::GetFileResource
+          (query, Orthanc::EmbeddedResources::SQLITE_PREPARE_INDEX);
+        db->Execute(query);
+ 
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion, expectedVersion);
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, 1);
+     }
+          
+      t.Commit();
+    }
+
+    db->Execute("PRAGMA ENCODING=\"UTF-8\";");
+
+    if (fast_)
+    {
+      // Performance tuning of SQLite with PRAGMAs
+      // http://www.sqlite.org/pragma.html
+      db->Execute("PRAGMA SYNCHRONOUS=NORMAL;");
+      db->Execute("PRAGMA JOURNAL_MODE=WAL;");
+      db->Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
+      db->Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
+      //db->Execute("PRAGMA TEMP_STORE=memory");
+    }
+
+    {
+      SQLiteTransaction t(*db);
+
+      if (!db->DoesTableExist("Resources"))
+      {
+        LOG(ERROR) << "Corrupted SQLite database";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);        
+      }
+
+      int version = 0;
+      if (!LookupGlobalIntegerProperty(version, *db, t, Orthanc::GlobalProperty_DatabaseSchemaVersion) ||
+          version != 6)
+      {
+        LOG(ERROR) << "SQLite plugin is incompatible with database schema version: " << version;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+
+      int revision;
+      if (!LookupGlobalIntegerProperty(revision, *db, t, Orthanc::GlobalProperty_DatabasePatchLevel))
+      {
+        revision = 1;
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
+      }
+
+      if (revision != 1)
+      {
+        LOG(ERROR) << "SQLite plugin is incompatible with database schema revision: " << revision;
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }      
+          
+      t.Commit();
+    }
+
+    return db.release();
+  }
+
+
+  SQLiteIndex::SQLiteIndex(const std::string& path) :
+    IndexBackend(new Factory(*this)),
+    context_(NULL),
+    path_(path),
+    fast_(true)
+  {
+    if (path.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  SQLiteIndex::SQLiteIndex() :
+    IndexBackend(new Factory(*this)),
+    context_(NULL),
+    fast_(true)
+  {
+  }
+
+
+  int64_t SQLiteIndex::CreateResource(const char* publicId,
+                                      OrthancPluginResourceType type)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "INSERT INTO Resources VALUES(NULL, ${type}, ${id}, NULL)");
+    
+    statement.SetParameterType("id", ValueType_Utf8String);
+    statement.SetParameterType("type", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetUtf8Value("id", publicId);
+    args.SetIntegerValue("type", static_cast<int>(type));
+    
+    statement.Execute(args);
+
+    return dynamic_cast<SQLiteDatabase&>(statement.GetDatabase()).GetLastInsertRowId();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/Plugins/SQLiteIndex.h	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Plugins/IndexBackend.h"
+
+namespace OrthancDatabases
+{
+  class SQLiteIndex : public IndexBackend 
+  {
+  private:
+    class Factory : public IDatabaseFactory
+    {
+    private:
+      SQLiteIndex&  that_;
+
+    public:
+      Factory(SQLiteIndex& that) :
+      that_(that)
+      {
+      }
+
+      virtual Dialect GetDialect() const
+      {
+        return Dialect_SQLite;
+      }
+
+      virtual IDatabase* Open()
+      {
+        return that_.OpenInternal();
+      }
+    };
+
+    OrthancPluginContext*  context_;
+    std::string            path_;
+    bool                   fast_;
+
+    IDatabase* OpenInternal();
+
+  public:
+    SQLiteIndex();  // Opens in memory
+
+    SQLiteIndex(const std::string& path);
+
+    void SetOrthancPluginContext(OrthancPluginContext* context)
+    {
+      context_ = context;
+    }
+
+    void SetFast(bool fast)
+    {
+      fast_ = fast;
+    }
+
+    virtual int64_t CreateResource(const char* publicId,
+                                   OrthancPluginResourceType type);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SQLite/UnitTests/UnitTestsMain.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,73 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Plugins/SQLiteIndex.h"
+#include "../../Framework/Plugins/IndexUnitTests.h"
+
+#include <Core/Logging.h>
+#include <Core/SystemToolbox.h>
+
+#include <gtest/gtest.h>
+
+
+
+TEST(SQLiteIndex, Lock)
+{
+  {
+    // No locking if using memory backend
+    OrthancDatabases::SQLiteIndex db1;
+    OrthancDatabases::SQLiteIndex db2;
+
+    db1.Open();
+    db2.Open();
+  }
+
+  Orthanc::SystemToolbox::RemoveFile("index.db");
+
+  {
+    OrthancDatabases::SQLiteIndex db1("index.db");
+    OrthancDatabases::SQLiteIndex db2("index.db");
+
+    db1.Open();
+    ASSERT_THROW(db2.Open(), Orthanc::OrthancException);
+  }
+
+  {
+    OrthancDatabases::SQLiteIndex db3("index.db");
+    db3.Open();
+  }
+}
+
+
+
+int main(int argc, char **argv)
+{
+  ::testing::InitGoogleTest(&argc, argv);
+  Orthanc::Logging::Initialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+  Orthanc::Logging::EnableTraceLevel(true);
+
+  int result = RUN_ALL_TESTS();
+
+  Orthanc::Logging::Finalize();
+
+  return result;
+}