Development

Another bug with lateral

XTended Oracle SQL - Fri, 2019-02-15 18:32

Compare the results of the following query with the clause “fetch first 2 rows only”

with 
 t1(a) as (select * from table(odcinumberlist(1,3)))
,t2(a,b) as (select * from table(ku$_objnumpairlist(
                                 sys.ku$_objnumpair(1,1),
                                 sys.ku$_objnumpair(1,2),
                                 sys.ku$_objnumpair(1,3),
                                 sys.ku$_objnumpair(3,1),
                                 sys.ku$_objnumpair(3,2),
                                 sys.ku$_objnumpair(3,3)
                                 )))
,t(id) as (select * from table(odcinumberlist(1,2,3,4,5,6,7)))
select
  *
from t,
     lateral(select t1.a,t2.b
             from t1,t2 
             where t1.a = t2.a 
               and t1.a = t.id
             order by t2.b
             fetch first 2 rows only
             )(+)
order by id;

        ID          A          B
---------- ---------- ----------
         1          1          1
         1          3          1
         2          1          1
         2          3          1
         3          1          1
         3          3          1
         4          1          1
         4          3          1
         5          1          1
         5          3          1
         6          1          1
         6          3          1
         7          1          1
         7          3          1

14 rows selected.

with this one (i’ve just commented out the line with “fetch-first-rows-only”:

with 
 t1(a) as (select * from table(odcinumberlist(1,3)))
,t2(a,b) as (select * from table(ku$_objnumpairlist(
                                 sys.ku$_objnumpair(1,1),
                                 sys.ku$_objnumpair(1,2),
                                 sys.ku$_objnumpair(1,3),
                                 sys.ku$_objnumpair(3,1),
                                 sys.ku$_objnumpair(3,2),
                                 sys.ku$_objnumpair(3,3)
                                 )))
,t(id) as (select * from table(odcinumberlist(1,2,3,4,5,6,7)))
select
  *
from t,
     lateral(select t1.a,t2.b
             from t1,t2 
             where t1.a = t2.a 
               and t1.a = t.id
             order by t2.b
--             fetch first 2 rows only
             )(+)
order by id;

        ID          A          B
---------- ---------- ----------
         1          1          2
         1          1          3
         1          1          1
         2
         3          3          2
         3          3          1
         3          3          3
         4
         5
         6
         7

11 rows selected.

Obviously, the first query should return less rows than second one, but we can see that it returned more rows and join predicate “and t1.a = t.id” was ignored, because A and B are not empty and “A” is not equal to t.ID.

Categories: Development

Lateral view decorrelation(VW_DCL) causes wrong results with rownum

XTended Oracle SQL - Fri, 2019-02-15 17:45

Everyone knows that rownum in inline views blocks many query transformations, for example pushing/pulling predicates, scalar subquery unnesting, etc, and many people use it for such purposes as a workaround to avoid unwanted transformations(or even CBO bugs).

Obviously, the main reason of that is different calculation of rownum:

If we pull the predicate “column_value = 3” from the following query to higher level

select * 
from (select * from table(odcinumberlist(1,1,1,2,2,2,3,3,3)) order by 1)
where rownum <= 2
  and column_value = 3;


COLUMN_VALUE
------------
           3
           3

we will get different results:

select * 
from (select *
      from (select * from table(odcinumberlist(1,1,1,2,2,2,3,3,3)) order by 1)
      where rownum <= 2
     )
where column_value = 3;

no rows selected

Doc ID 62340.1

[collapse]

But we recently encountered a bug with it: lateral view with ROWNUM returns wrong results in case of lateral view decorrelation.
Compare results of this query with and without no_decorrelation hint:

with 
 t1(a) as (select * from table(odcinumberlist(1,3)))
,t2(b) as (select * from table(odcinumberlist(1,1,3,3)))
,t(id) as (select * from table(odcinumberlist(1,2,3)))
select
  *
from t,
     lateral(select/*+ no_decorrelate */ rownum rn 
             from t1,t2 
             where t1.a=t2.b and t1.a = t.id
            )(+)
order by 1,2;

        ID         RN
---------- ----------
         1          1
         1          2
         2
         3          1
         3          2
with 
 t1(a) as (select * from table(odcinumberlist(1,3)))
,t2(b) as (select * from table(odcinumberlist(1,1,3,3)))
,t(id) as (select * from table(odcinumberlist(1,2,3)))
select
  *
from t,
     lateral(select rownum rn 
             from t1,t2 
             where t1.a=t2.b and t1.a = t.id
            )(+)
order by 1,2;

        ID         RN
---------- ----------
         1          1
         1          2
         2
         3          3
         3          4

Of course, we can draw conclusions even from these results: we can see that in case of decorrelation(query with hint) rownum was calculated before the join. But to be sure we can check optimizer’s trace 10053:

Final query after transformations:

******* UNPARSED QUERY IS *******
SELECT VALUE(KOKBF$2) "ID", "VW_DCL_76980902"."RN" "RN"
  FROM TABLE("ODCINUMBERLIST"(1, 2, 3)) "KOKBF$2",
       (SELECT ROWNUM "RN_0", VALUE(KOKBF$0) "ITEM_3"
          FROM TABLE("ODCINUMBERLIST"(1, 3)) "KOKBF$0",
               TABLE("ODCINUMBERLIST"(1, 1, 3, 3)) "KOKBF$1"
         WHERE VALUE(KOKBF$0) = VALUE(KOKBF$1)
        ) "VW_DCL_76980902"
 WHERE "VW_DCL_76980902"."ITEM_3"(+) = VALUE(KOKBF$2)
 ORDER BY VALUE(KOKBF$2), "VW_DCL_76980902"."RN"

*************************

[collapse]

I’ll modify it a bit just to make it more readable:
we can see that

select
  *
from t,
     lateral(select rownum rn 
             from t1,t2 
             where t1.a=t2.b and t1.a = t.id)(+)
order by 1,2;

was transformed to

select
  t.id, dcl.rn
from t,
     (select rownum rn 
      from t1,t2 
      where t1.a=t2.b) dcl
where dcl.a(+) = t.id
order by 1,2;

And it confirms that rownum was calculated on the different dataset (t1-t2 join) without join filter by table t.
I created SR with Severity 1 (SR #3-19117219271) more than a month ago, but unfortunately Oracle development do not want to fix this bug and moreover they say that is not a bug. So I think this is a dangerous precedent and probably soon we will not be able to be sure in the calculation of rownum and old fixes…

Categories: Development

Oracle ADF or Oracle JET - An Update

Shay Shmeltzer - Thu, 2019-02-14 14:36

Back in 2015 I wrote a blog entry to help our customers get a clear picture about the Oracle ADF vs Oracle JET "debate", over the years I kept pointing people to this writeup as a simple summary of what we are aiming to do and as a guideline for picking the technology you should use. However, the technology world is an ever changing place, and I believe that it is time to do a bit of an update of that entry to reflect the current status.

While the basic distinctions between the two technologies have not changed, specifically: 

  • ADF for Java developer, JET for JavaScript developer
  • ADF is server side framework, JET is client side focused
  • ADF is both front and back-end framework, JET is UI layer only

Some of the surrounding eco-system has changed and evolved.

Client-Centric Architecture Momentum

Over the past several years we saw an increase in the adoption of UI development that is focused around JavaScript-based client layer. This is something you can see for example in the results from the Stackoverflow yearly survey - back in 2016 JavaScript was used by 54% of developers and in 2018 the number stood at ~70%

Today, most of the new projects that we see being developed will pick up a JavaScript based UI layer as their solution for front end development. The adoption of this JS/HTML UI is not limited to web apps, many mobile apps also leverage the architecture to create mobile optimized UIs deployed on devices using either a hybrid or a Progressive Web Application (PWA) architecture. Development organizations like the fact that a client-centric JavaScript/HTML architecture allows them to use a single set of skills to support multi-channel apps. 

Back in 2015 many of the Oracle PaaS services were already leveraging this architecture along with Oracle JET - Oracle's Toolkit for building JavaScript based UIs. Today this trend started to extend to Oracle SaaS - Oracle's cloud apps offering. Several of the modules in Oracle SaaS have introduced UIs built with Oracle JET - leveraging its capabilities to create engaging and dynamic user interfaces. In the coming years you are going to see more and more modules offering new UIs based on Orale JET. This is a transition for Oracle's SaaS org from server-side generation UI to a client centric architecture. 

In parallel Oracle JET also added a bunch of capabilities targeting mobile specific scenarios including the ability to render the components to look like native controls on both iOS and Android, the ability to package apps as hybrid mobile apps, and support for offline usage patterns. All those capabilities are further pushing the usage of Oracle JET at Oracle as the UI layer for the various mobile apps we deliver.

One thing to keep in mind, since JET only provides the UI layer, there is still a need to build the backend of the application. This backend will handle data access and business logic - and it will then expose REST services that will be consumed by the JET UI. The existing backend layer for the Oracle SaaS apps was developed with Oracle ADF Business Components, and now they are able to leverage the investment they already did in that layer and expose these components through REST services. This way they get to keep the complex logic already implemented in that layer and just create a new modernized UI layer. 

If you track down the "what's new" document for the versions of ADF that we released since 2015 you'll see that enhancing the out-of-the-box functionality we provide around REST enablement has been a focus area for us. As a result of this effort, when you are exposing ADF BC as REST you get capabilities such as pagination, query by example, dynamic sorting, relationship management, and more built-into the services you expose.

This approach is something that ADF customers should consider if they decide to adopt the trend of new UIs being built with JavaScript based toolkit. Regardless of the UI framework that you'll choose, remember that the investment you made in the ADF backend can still be leveraged easily.

Better Tooling

One of the other difference I quoted between ADF and JET was the development experience. ADF was focused on providing a more visual and declarative approach to Java developers, while JET had you use code editors to manually write your code.

This is another thing that changed since 2015. Oracle now offers a development platform for JET applications that is focused on a more visual way of building the app - this is Oracle Visual Builder Cloud Service. If you haven't seen a demo before - take 10 minutes and watch this video as an example. As you can see, Visual Builder provides a WYSIWYG UI design, action flow diagrams for business logic, page flow diagrams, property inspectors and more. All this while still allowing you to directly access the code when needed for direct manipulation. 

Visual Development with VBCS

Image 1 - Visual Oracle JET Development in Oracle Visual Builder (Components, Structure Pane, Visual Layout Editor, Property Inspector)

With this environment you can become much more productive building UIs that leverage the power of Oracle JET. Note that Visual Builder is not just about simplifying the layout of your pages, it is also simplifying how you work with variables, binding and business logic - reducing the amount of coding you need to do.

Visual Builder also simplifies the learning curve for people who are new to JavaScript development - making the transition to this new technology smoother. Our SaaS teams have also decided to leverage VB to help them deliver new generations of UI faster.

For customers who already have ADF based applications, another appealing aspect of VB is its ability to understand the structure of ADF BC based REST services and the functionality they provide. Check out this demo to see how you can quickly use Visual Builder to build a JET based mobile app on top of ADF BC REST backend in a declarative way.

A little side note - beyond the ability of VB to create UIs, you can also use it to quickly create tables in a database and business objects (including business logic) on top of them that are exposed as REST services. The technology that drives that layer of VB is based on the same ADF Business Components - another thing that will be of course familiar to ADF developers if they decide to pick up Visual Builder.

Summary

So what should you do if you are an Oracle ADF customer today? As always the answer is "it depends" :-)

The first thing to think about is - "Does Oracle ADF answers my needs and does the application I built with it work as expected?" - assuming that the answer to both is positive, then you can continue as you were.

If, however, there are technical or architectural needs where your existing ADF app doesn't provide the answers you need (for example on the UI side or the mobile device support needs), looking at Oracle JET and Visual Builder is definitely something you should do. Also if you are about to embark on a new project, you might want to consider if a new client-centric architecture for the UI layer is something you can benefit from, and if you think it is - again try out JET and VB and see how you like them. In both cases remember that you can leverage your existing investments in the business objects layer and expose it as REST services to be consumed by new UIs. In fact you can have the two apps running in parallel.

JET and ADF side by side

Image 2- JET based architecture (MVVM) compared to ADF Architecture (MVC) - sharing business services

One more note, many developers like to keep their skills fresh and learn new things, if you are an Oracle ADF developer, I think that picking up Visual Builder and playing with it can get you on the road to learning new JavaScript skills quite quickly while still keeping you in a fun friendly visual cloud IDE - give it a try.

 

Categories: Development

Region only shown in development mode in Oracle APEX

Dimitri Gielis - Sun, 2019-02-10 11:51
In the last months, I had to look up several times in different projects how to show a region only when I was logged in into the App Builder in Oracle Application Express (APEX). So I thought to write a quick post on it.

In one project we had to log in with different users to test the behavior of the authorization schemes, so people saw the things they should see and could do the things they are allowed to do. As the logins where not straight forward we created a region with those test credentials. Other people were testing too with their own credentials, so we really wanted to keep the original login page, and decided to just add a region on the page we would only see when we were logged in into APEX itself.

Today I added some new pages to an app and wanted to make sure the navigation to those pages were only visible to me. I know, you should do this in DEV, and then when all is fine, propagate the app to TEST and PROD. The truth is, I've some applications that only exist in one environment and so I update straight in "production". Those apps are still backed up automatically every night, so worst case I can always take the version of the previous day. But just to be clear, this is not good practice ;)

So how do you show a region only when you are in development mode in Oracle APEX?

You go to the Conditions section of your region, list entry or any component in APEX really, and add a PL/SQL Expression: apex_application.g_edit_cookie_session_id is not null


It would be cool if there was a condition type "Development Mode", but maybe I'm the only one needing this...

Typically you would use Build Options (see Shared Components) to include or exclude certain functionality in your APEX app, but in the above two use cases, it won't really work.


Another enhancement would be that the Status of the Build option would include "Dev Only" next to Include and Exclude.
Categories: Development

Oracle Linux hangs after “probing EDD” in Oracle Cloud

XTended Oracle SQL - Tue, 2019-02-05 03:38

Just short note: If your imported Oracle Linux image hangs on boot in the Oracle cloud, just set GRUB_DISABLE_UUID=”true” in /etc/default/grub

Categories: Development

The best free database... Google is wrong!

Dimitri Gielis - Mon, 2019-01-28 12:23
When you search on Google for "the best free database", the below is what you get (search done 11-NOV-2018 and again on 12-JAN-2019 and 28-JAN-2019). To my surprise, there's no Oracle on the list? The reason is Google took the answer from this review. As I don't want people to see this screenshot, I put in red what is wrong with the answer, so in case Google shows images, and people don't read this blog post, they don't get the wrong answer ;)

The above is so wrong for me, the best free database in the world is Oracle Express Edition (XE). Oracle released XE18c on October 19th, 2018. This database is unbelievable. You basically get an Enterprise Edition version and almost all options are turned on! It's amazing, the only restrictions you have is on the amount of RAM (2GB) and disk space (12GB). You even have the pluggable database architecture and can create 3 PDBs (pluggable databases).
In my opinion, there is no other free database in the world that will beat this. Below I will go in more detail why I like this Oracle XE18c so much, but first, let me show you Google actually knows the right answer too.

Google says "People also ask": "What are the top 5 databases available on the market?" and here Oracle is number one. The other question is "What is the best database software for small businesses?" Oracle number one again. If the question would be "What is the best database software for enterprise businesses?" Oracle is number one too, this is common knowledge.


Google's algorithm to answer the first question, just got it wrong. How can Oracle be number one and be the best, but not in the free section, whereas their best database is available for free? :)

Google allows you to comment on their search results, which I did:



Why do I like Oracle XE 18c so much?

When we talk about Oracle XE, we really talk about the full Oracle database in general. Yes, there are a couple of limitations, but nevertheless, you get the full feature set of the Oracle database! All the good stuff why Oracle shines is there: to have the best performance you can use partitioning and online index rebuilds (and in the future automatic index creation!), to increase high availability you have the full flashback technology to your disposal, for security Oracle has VPD, Real Application Security, Database Vault... Oracle plans to release a new version of XE every year too, so you have always the latest and greatest.

I should write another blog post why I like the Oracle database so much, but I encourage you to just try it and decide yourself.

Getting started with Oracle XE

If you just want to try Oracle XE, most likely the easiest way without hitting your system, is to go with the Oracle docker container. Here're the steps to get Oracle XE running in an Oracle docker container.

If you don't have Docker and Git yet, download and install first.

Open a Terminal or Command Prompt and run following commands:

git clone https://github.com/oracle/docker-images.git

cd docker-images/OracleDatabase/SingleInstance/dockerfiles

Download Oracle XE
Copy the oracle-database-xe-18c-1.0-1.x86_64.rpm in the docker-images/OracleDatabase/SingleInstance/dockerfiles/18.4.0 directory

Move on by running following command:

 ./buildDockerImage.sh -v 18.4.0 -x -i



docker images

docker run --name OracleXE -p 1521:1521 -p 8080:8080 -e ORACLE_SID=XE -e ORACLE_PWD=oracle -v /Users/dgielis/git/docker-images/OracleDatabase/SingleInstance/dockerfiles:/opt/oracle/oradata oracle/database:18.4.0-xe

    ...

And voila, you are done! (more info on the Oracle Docker images)

To get a view in your database you can use Oracle SQL Developer. Here's how you connect to it:



The next thing would be to install Oracle APEX, so you don't only have the best database in the world, but also the best low code platform in the world, which works absolutely awesome with the Oracle database.
Categories: Development

Trying to decide which APEX events to attend? Here's where I'll be.

Dimitri Gielis - Fri, 2019-01-25 11:28
I love going to conferences, to catch up with friends, be part of the vibrant Oracle APEX community, gain and share knowledge and of course demo APEX Office Print (AOP).

Conferences are a great way to get some new ideas and insights. You learn not only from the content but even more from the different cultures and background of people and the way they approach things.

My schedule till June looks like this:

ITOUG Tech Day Milano, Italy: 30-JAN-2019

This day is part of the ITOUG Tech Days. I've never presented in Italy, so I really look forward to this first time. I'll present on "Bringing your Oracle Database alive with APEX".

APEX Meetup Düsseldorf, Germany: 5-FEB-2019

As we did some major releases with AOP 18.1 and 19.1 and we have many customers in Germany, Niels asked to present on Reporting in APEX. As it's a meetup, we have more room to make it really interactive, so if you are in the region of Düsseldorf and Ratingen, stop by and ask me any question. I'll show some cool tricks of AOP and will give some insight what's coming later in the year.

APEX World, the Netherlands: 25/26-MAR-2019

I don't think I've ever missed a version of APEX World. It became the biggest APEX only conference in the world, and it's basically in my backyard :) This year is special too, as it's the 10th year anniversary! My company, APEX R&D, has been a long time sponsor of the event. This is the only conference where we also have a booth, so you can stop by any time you want and ask us many questions :) At our booth, we show the latest and greatest dev build of APEX Office Print (AOP)!
I'll give my "Augmented Reality & Virtual Reality with APEX" presentation this year.



APEX Alpe Aldria, Croatia: 12-APR-2019

Last year this conference got its first edition and it was a great success. My friends Peter, Aljaz, and Dario really know how to make a great conference. Last year AOP sponsored and Sunil, our lead backend architect of AOP, was there to answer any questions. This year most of our team is going. I'll give a presentation "APEX Reporting Tips & Tricks", similar to the webinar I did for AUSOUG.

APEX Connect, Germany: 7/9-MAY-2019

The last couple of years I've presented at this conference. APEX is being used a lot in Germany, so it's great to see so many use cases of APEX. This year I was asked to do my "Augmented Reality & Virtual Reality with APEX" presentation. In our office we have 3 VR systems running (with HTC Vive) and are doing some research on cool use cases. I dream one day we can develop our APEX apps like Tom Cruise did in the movie Minority Report :)



ODTUG KScope, Seattle, US: 23/27-JUN-2019

Since my first attendance in 2006, this is my favorite conference. It's the go-to place to meet the APEX development team, and so many smart people you meet daily on the internet.
It's unsure yet what I'll present on this year, reviews are still ongoing, but for sure there are many good presentations there and the latest and greatest of the APEX Dev Team. If you can ever go to this conference, I would definitely recommend it!


If you are at one of the above events and you want to chat, don't hesitate to stop me. I would love to meet you. I'm thankful you read my blog and I would love to hear more about you.

Categories: Development

APEX Office Print (AOP) 19.1: Printing and Exporting made easy in Oracle APEX

Dimitri Gielis - Wed, 2019-01-23 09:34
To start the new year in an awesome way, we released APEX Office Print (AOP) 19.1 on January 11th. I typically don't blog about every new AOP release, but this is an important release in our history of the product.

Our dream is that every developer can use AOP, that is why in AOP 18.1 (released September 2018) we made a Free Cloud Tier.

With AOP 19.1 we go one step further... from now on you can run AOP in Developer Mode, which means you can call our cloud as much as you like in this mode. Your number of reports/credits in your plan are not touched at all. When you want to test AOP or are developing some new reports, you can use development mode so you don't need to use your credits for that.

We had been thinking about this setting for some time, but Jon from JMJ Cloud reminded us how important it is for you in his nice blog post: APEX Office Print – Is it the APEX printing solution we have all been waiting for?

Just like in previous versions we added a number of new features, fixed some bugs and made several enhancements. You can view the release notes for every version here.

AOP 19.1 is again a free update for all our users, Cloud and On-Premises (with a valid maintenance contract).

We love to listen to our customers and help them as good as we can to make their projects a success. That is why we like to release frequently, so creating the reports you want is easy, fast and integrated with Oracle APEX.

In this post, I want to highlight some of the APEX Office Print (AOP) features that I believe make a difference, and why AOP became the go-to solution when you want to print and export your data in Oracle APEX. If you want to see AOP in action, definitely check out the AOP Sample App.

Architecture

Just like BI Publisher or XSL-FO, AOP has a server component. The difference is that AOP is one executable, has a built-in web server which can autoscale depending the number of CPU cores and it listens on incoming requests with a JSON payload. The AOP Server is very simple to install, upgrade and maintain, yet flexible and scalable out-of-the-box.

To make it really easy to do calls to the AOP Server, we built a PL/SQL API on top of the REST interface. As we love Oracle APEX so much, we built an APEX Plug-in on top of the PL/SQL API.

Our idea is that you are up-and-running in less than 5 minutes.



Integration with Oracle APEX

AOP is the most integrated solution for APEX apps on the market. We went much further than the built-in BI Publisher and XSL-FO support. AOP understands your Classic Reports, Interactive Reports, and Grids, Charts and Calendars. AOP lets you print or export your Interactive Report and Grid exactly as you see it on the screen, with highlights, computations, breaks, group by etc.


In the APEX Plug-in you specify the static id of the region(s) and AOP will do the rest.


The APEX Plug-in is really flexible and the easiest way to get your reports done in no time. 
You specify your template, the data source(s) and your output, that's it.

Based on Templates

AOP is template driven, you basically create a template in your favorite editor. AOP supports templates in Word, Excel, Powerpoint, PDF, HTML, Markdown, Text and CSV.
In the template, you use specific tags AOP understands. You find an overview of tags in the AOP documentation.



PDF Tools

As many people generate PDF documents, since AOP 18 we are focussing to include more and more PDF-specific features. AOP allows you to generate PDFs from almost any file format, even images, you can merge PDFs, split them, password protect the PDF and you can even include a watermark.


We also have a special PDF, called Single Page PDF. Some people don't know the size of the document, so they just want a PDF with a variable size. For example when you get a receipt in a restaurant, depending on the number of dishes and drinks the PDF is different in size.

One more thing... AOP Web Editor

We are developers ourselves and have been using AOP since 2015. Sometimes you don't know what's going on, so from day one we heavily instrumented AOP to see exactly what it's doing and give you all the tools to make debugging fun. The Web Editor allows you to create documents really fast, look at debug info, see the different requests and try different options really fast.



Support

We love our customers and find support extremely important as we want you to be successful. You can contact us through our support email or through the new AOP Slack Channel on apex.world.

Final words

The above is just a small set of features I believe make a huge difference compared to any other engine. You don't have to take my word for it, just give AOP a try with our free version and development mode and decide yourself :) And know you are not alone, we are proud of every single customer we have from large customers like Siemens, NASA and even Oracle, to smaller customers like Storm-Petrel (who use AOP extensively!).

Categories: Development

Upgrading of Oracle APEX 18.x and future

Dimitri Gielis - Mon, 2019-01-21 08:43
Late September 2018 Oracle Application Express (APEX) 18.2 was released.

Since 2018, Oracle adopts a new versioning for their software, and APEX is following that. The plan of the Oracle APEX development team is to do two releases a year which carry the year and the release number of that year e.g. 18.1, 18.2, 19.1.

I like this new way of providing us with new releases. You don't know the exact timing of the releases, but you know the development team will be close to release version 1 in March/April and version 2 in September/October timeframe. In between you can download one-of patches or patch set releases through Oracle Support.

However there's one important change compared to before you have to be aware of:
every new version of Oracle APEX will be a complete install, with its own schema.

The biggest benefit of this approach is that the downtime to upgrade an Oracle APEX instance can be very minimal and the install is fast. You can read more about it in Maximizing Uptime During an Application Express Upgrade. I believe the Oracle Cloud is a big driver for this, but it benefits us all.

Another benefit is that if you can download and install all APEX releases in Oracle XE more easily. Before if you had installed APEX 5.1.0 and wanted to upgrade to APEX 5.1.2, 5.1.3, 5.1.4 you had to download the software from Oracle Support. But you could only do that with a valid support contract. If you wanted a complete free system with Oracle XE, you could download the latest version of APEX and do a new install and migrate over the workspaces and apps. Since APEX 18 you can always install those versions and APEX itself is doing the migration for you.

Now the biggest disadvantage of this approach is that you can't easily export and import your apps and plug-ins to a different version. Before when you might have Oracle APEX 5.1.0 (released Dec. 2016) in one environment and Oracle APEX 5.1.4 (released Dec. 2017) in another (or anything in between) and you could easily export and import between those environments. Oracle APEX exports have always been compatible till the second dot, so you could export import from any 5.1.x to another 5.1.x. You can import an APEX 18.1 version in an APEX 18.2 environment, but not the other way.

We typically move fast to a new version of APEX as we have to support those versions for APEX Office Print. I also blogged how we upgrade major releases by running multiple PDBs. With APEX 5.0, released in April 2015 and APEX 18.1 released in May 2018, we covered over 3 years in 3 PDBs (1 PDB for every major release of APEX 5.0, 5.1, 18.1). With the new version numbers, with 3 PDBs we cover a maximum of 1.5 years.

If you are aware of this, you can plan your application releases with it. And these new release numbers will make customers probably want to upgrade faster, or at least once a year as numbers go up fast :) As developers it's not only nice to work with new technology, it's also more productive for everyone.

Oracle APEX is not the only software in our stack right? ORDS, SQL Developer, Oracle Database, they follow the new release numbers. ORDS released a few days ago 18.4. The ORDS/SQL Developer/SQLcl team releases every quarter and typically use the quarter of development in their release number.

Tim Hall wrote a nice article on his thoughts on the new release numbers for the Oracle Database.

I love getting new versions and play with the new features or not wait long on bug fixes. We release frequently with APEX Office Print (AOP) too, 10 days ago we released AOP 19.1, but that is for another blog post!

Happy upgrading :)
Categories: Development

Mind Over Matter

Greg Pavlik - Fri, 2019-01-18 18:13
"Plotinus gave exquisitely refined expression to the ancient intuition that the material order is not the basis of the mental, but rather the reverse. This is not only an eminently rational intuition; it is perhaps the only truly rational picture of reality as a whole. Mind does not emerge from mindless matter, as modern philosophical fashion would have it. The suggestion that is does is both a logical impossibility and a phenomenological absurdity. Plotinus and his contemporaries understood that all the things that most essentially characterize the act of rational consciousness—its irreducible unity of apprehension, its teleological structure, the logical syntax of reasoning, and on and on—are intrinsically incompatible with, and could not logically emerge from, a material reality devoid of mind. At the same time, they could not fail to notice that there is a constant correlation between that act of rational consciousness and the intelligibility of being, a correlation that is all but unimaginable if the structure and ground of all reality were not already rational. Happily, in Plotinus’s time no one had yet ventured the essentially magical theory of perception as representation. Plotinus was absolutely correct, therefore, to attempt to understand the structure of the whole of reality by looking inward to the structure of the mind; and he was just as correct to suppose that the reciprocity between the mind and objective reality must indicate a reality simpler and more capacious than either: a primordial intelligence, Nous, and an original unity, the One, generating, sustaining, and encompassing all things. And no thinker of late antiquity pursued these matters with greater persistence, rigor, and originality than he did."

DB Hart commenting on the new translation of Plotinus's Enneads.

Displaying an "Unsaved Changes" Warning in Visual Builder

Shay Shmeltzer - Thu, 2019-01-10 17:14

End-users are strange. Sometime they need the system we are developing to remind them when they do silly things. For example some users want us to remind them if they are trying to navigate away from a page where they did changes to data, but didn't click "save". Below I'll show you an approach for implementing such a behavior in Oracle Visual Builder Cloud Service.

What we are going to do is cancel navigation actions until users acknowledge that they are ok to leave the page.

I will leave it up to you to decide when this should go into effect. While some people might claim that this should be the default behavior across the app, this is debatable. For example, if I go into a "create record" type of page, and then without doing any changes decide to leave that page - should I be prompted for a "you have unsaved changes" message? Isn't me leaving the page the equivalent of saying - I don't need to do changes? As you can see the decision is not clear cut - so in the demo below I let you control when we start this "changes were made" status simply by pressing a button. In real applications these can be done for example when a value in a field changes. At the end of the day, all you need to do is set a boolean variable that tracks whether we are now in "changes were made" status.

In the demo I added a simple dialog to the shell page (the page that acts as the containing template to the rest of the pages) - this dialog has a "you have unsaved changes, are you sure you want to leave?" type of warning and two buttons "Yes" and "No". (Quick tip/reminder - you can see how to add a dialog to a page here, and don't forget to include the popup import in the page's json file).

I add an action chain to the shell page that will be invoked on the vbBeforeExit event - in there I check the value of the "changes made" variable and if changes were made - I show the dialog. Then I use a return action to return an object type variable that has a boolean variable called "cancelled" set to true.  Returning such an object tells the flow to stop the navigation.

Now all I needed to add were action chains to the buttons for "yes" and "no" to close the dialog, and for the "yes" scenario to also set the changes made boolean variable to no - so the next time we click to navigate away we don't show the dialog.

Check out the video to see the runtime behavior and the various parts that make up the solution.

Categories: Development

Robinson Jeffers

Greg Pavlik - Thu, 2019-01-10 12:25
Today is the birthday of one of the most under-rated American poets (in my view, one of the best we have produced), the builder of Tor House and Hawk Tower, which can still be visited in Carmel, California. A timely documentary on an American genius.


The website for Tor House visits, a fascinating experience:

http://www.torhouse.org/

Top-N again: fetch first N rows only vs rownum

XTended Oracle SQL - Sun, 2018-12-30 05:04

Three interesting myths about rowlimiting clause vs rownum have recently been posted on our Russian forum:

  1. TopN query with rownum<=N is always faster than "fetch first N rows only" (ie. row_number()over(order by ...)<=N)
  2. “fetch first N rows only” is always faster than rownum<=N
  3. “SORT ORDER BY STOPKEY” stores just N top records during sorting, while “WINDOW SORT PUSHED RANK” sorts all input and stores all records sorted in memory.

Interestingly that after Vyacheslav posted first statement as an axiom and someone posted old tests(from 2009) and few people made own tests which showed that “fetch first N rows” is about 2-3 times faster than the query with rownum, the final decision was that “fetch first” is always faster.

First of all I want to show that statement #3 is wrong and “WINDOW SORT PUSHED RANK” with row_number works similarly as “SORT ORDER BY STOPKEY”:
It’s pretty easy to show using sort trace:
Let’s create simple small table Tests1 with 1000 rows where A is in range 1-1000 (just 1 block):

create table test1(a not null, b) as
  select level, level from dual connect by level<=1000;

alter session set max_dump_file_size=unlimited;
ALTER SESSION SET EVENTS '10032 trace name context forever, level 10';

ALTER SESSION SET tracefile_identifier = 'rownum';
select * from (select * from test1 order by a) where rownum<=10;

ALTER SESSION SET tracefile_identifier = 'rownumber';
select * from test1 order by a fetch first 10 rows only;

And we can see from the trace files that both queries did the same number of comparisons:

rownum:
----- Current SQL Statement for this session (sql_id=bbg66rcbt76zt) -----
select * from (select * from test1 order by a) where rownum<=10

---- Sort Statistics ------------------------------
Input records                             1000
Output records                            10
Total number of comparisons performed     999
  Comparisons performed by in-memory sort 999
Total amount of memory used               2048
Uses version 1 sort
---- End of Sort Statistics -----------------------

[collapse]
row_number

----- Current SQL Statement for this session (sql_id=duuy4bvaz3d0q) -----
select * from test1 order by a fetch first 10 rows only

---- Sort Statistics ------------------------------
Input records                             1000
Output records                            10
Total number of comparisons performed     999
  Comparisons performed by in-memory sort 999
Total amount of memory used               2048
Uses version 1 sort
---- End of Sort Statistics -----------------------

[collapse]

Ie. each row (except first one) was compared with the biggest value from top 10 values and since they were bigger than top 10 value, oracle doesn’t compare it with other TopN values.

And if we change the order of rows in the table both of these queries will do the same number of comparisons again:

from 999 to 0

create table test1(a not null, b) as
  select 1000-level, level from dual connect by level<=1000;

alter session set max_dump_file_size=unlimited;
ALTER SESSION SET EVENTS '10032 trace name context forever, level 10';

ALTER SESSION SET tracefile_identifier = 'rownum';
select * from (select * from test1 order by a) where rownum<=10;


ALTER SESSION SET tracefile_identifier = 'rownumber';
select * from test1 order by a fetch first 10 rows only;

[collapse]
rownum

----- Current SQL Statement for this session (sql_id=bbg66rcbt76zt) -----
select * from (select * from test1 order by a) where rownum<=10

---- Sort Statistics ------------------------------
Input records                             1000
Output records                            1000
Total number of comparisons performed     4976
  Comparisons performed by in-memory sort 4976
Total amount of memory used               2048
Uses version 1 sort
---- End of Sort Statistics -----------------------

[collapse]
row_number

----- Current SQL Statement for this session (sql_id=duuy4bvaz3d0q) -----
select * from test1 order by a fetch first 10 rows only

---- Sort Statistics ------------------------------
Input records                             1000
Output records                            1000
Total number of comparisons performed     4976
  Comparisons performed by in-memory sort 4976
Total amount of memory used               2048
Uses version 1 sort
---- End of Sort Statistics -----------------------

[collapse]

We can see that both queries required much more comparisons(4976) here, that’s because each new value is smaller than the biggest value from the topN and even smaller than lowest value, so oracle should get right position for it and it requires 5 comparisons for that (it compares with 10th value, then with 6th, 3rd, 2nd and 1st values from top10). Obviously it makes less comparisons for the first 10 rows.

Now let’s talk about statements #1 and #2:
We know that rownum forces optimizer_mode to switch to “first K rows”, because of the parameter “_optimizer_rownum_pred_based_fkr”

SQL> @param_ rownum

NAME                               VALUE  DEFLT  TYPE      DESCRIPTION
---------------------------------- ------ ------ --------- ------------------------------------------------------
_optimizer_rownum_bind_default     10     TRUE   number    Default value to use for rownum bind
_optimizer_rownum_pred_based_fkr   TRUE   TRUE   boolean   enable the use of first K rows due to rownum predicate
_px_rownum_pd                      TRUE   TRUE   boolean   turn off/on parallel rownum pushdown optimization

while fetch first/row_number doesn’t (it will be changed after the patch #22174392) and it leads to the following consequences:
1. first_rows disables serial direct reads optimization(or smartscan on Exadata), that’s why the tests with big tables showed that “fetch first” were much faster than the query with rownum.
So if we set “_serial_direct_read”=always, we get the same performance in both tests (within the margin of error).

2. In cases when index access (index full scan/index range scan) is better, CBO differently calculates the cardinality of underlying INDEX FULL(range) SCAN:
the query with rownum is optimized for first_k_rows and the cardinality of index access is equal to K rows, but CBO doesn’t reduce cardinality for “fetch first”, so the cost of index access is much higher, compare them:

rownum
SQL> explain plan for
  2  select *
  3  from (select * from test order by a,b)
  4  where rownum<=10;

--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |            |    10 |   390 |     4   (0)| 00:00:01 |
|*  1 |  COUNT STOPKEY                |            |       |       |            |          |
|   2 |   VIEW                        |            |    10 |   390 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS BY INDEX ROWID| TEST       |  1000K|    12M|     4   (0)| 00:00:01 |
|   4 |     INDEX FULL SCAN           | IX_TEST_AB |    10 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=10)

[collapse]
fetch first

SQL> explain plan for
  2  select *
  3  from test
  4  order by a,b
  5  fetch first 10 rows only;

-----------------------------------------------------------------------------------------
| Id  | Operation                | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |    10 |   780 |       |  5438   (1)| 00:00:01 |
|*  1 |  VIEW                    |      |    10 |   780 |       |  5438   (1)| 00:00:01 |
|*  2 |   WINDOW SORT PUSHED RANK|      |  1000K|    12M|    22M|  5438   (1)| 00:00:01 |
|   3 |    TABLE ACCESS FULL     | TEST |  1000K|    12M|       |   690   (1)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY "TEST"."A","TEST"."B")<=10)

[collapse]
fetch first + first_rows

SQL> explain plan for
  2  select/*+ first_rows */ *
  3  from test
  4  order by a,b
  5  fetch first 10 rows only;

--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |            |    10 |   780 | 27376   (1)| 00:00:02 |
|*  1 |  VIEW                         |            |    10 |   780 | 27376   (1)| 00:00:02 |
|*  2 |   WINDOW NOSORT STOPKEY       |            |  1000K|    12M| 27376   (1)| 00:00:02 |
|   3 |    TABLE ACCESS BY INDEX ROWID| TEST       |  1000K|    12M| 27376   (1)| 00:00:02 |
|   4 |     INDEX FULL SCAN           | IX_TEST_AB |  1000K|       |  2637   (1)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY "TEST"."A","TEST"."B")<=10)

[collapse]
fetch first + index

SQL> explain plan for
  2  select/*+ index(test (a,b)) */ *
  3  from test
  4  order by a,b
  5  fetch first 10 rows only;

--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |            |    10 |   780 | 27376   (1)| 00:00:02 |
|*  1 |  VIEW                         |            |    10 |   780 | 27376   (1)| 00:00:02 |
|*  2 |   WINDOW NOSORT STOPKEY       |            |  1000K|    12M| 27376   (1)| 00:00:02 |
|   3 |    TABLE ACCESS BY INDEX ROWID| TEST       |  1000K|    12M| 27376   (1)| 00:00:02 |
|   4 |     INDEX FULL SCAN           | IX_TEST_AB |  1000K|       |  2637   (1)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY "TEST"."A","TEST"."B")<=10)

[collapse]

So in this case we can add hints “first_rows” or “index”, or install the patch #22174392.

ps. I thought to post this note later, since I hadn’t time enough to add other interesting details about the different TopN variants, including “with tie”, rank(), etc, so I’ll post another note with more details later.

Categories: Development

Connecting to Oracle Autonomous Transaction Processing (ATP) from Developer Cloud Service

Shay Shmeltzer - Mon, 2018-12-10 18:10

The latest and greatest flavor of the Oracle Database in the cloud is Oracle Autonomous Transaction Processing (ATP). One of the Autonomous DB flavors that, it is optimized for OLTP (On-Line Transaction Processing) Applications - the type that you and I usually work on.

One new feature in the world of ATP is the way that you connect to the DB, connection is done leveraging wallets to make sure that your data is secured even though you are connecting over public internet. Here are instructions on how to get such a wallet file for your instance of ATP.

We introduced an enhancement to the latest version of Developer Cloud Service that allows you to connect to ATP from your CI/CD automation jobs. This can help you automate CI/CD for SQL scripts that you need to run against that DB.

As I mentioned in past blogs, DevCS has built in support for the SQLcl utility, allowing you to run SQL scripts against an Oracle database as part of your CI/CD chain. If you want to connect the SQLcl utility in DevCS to ATP, it will need to have access to your wallet.zip file. You can achieve this by uploading the file into your git repository.

Then in your SQLcl configuration you'll specify the user/pass like before, and then point the field titled Credentials File to the wallet.zip file location. (in the screenshot below, the zip file is at the top of the git repo connected to the build - so there is no need to add a path). In the next field, titled connection string, you specify the name used in the wallet's tnsnames.ora file to connect to the DB.

Now you can continue as usual and provide inline SQL or point to SQL files from your git repository.

ATP Connection Definition

 

Categories: Development

Docker with Oracle database: install patches automatically

XTended Oracle SQL - Thu, 2018-12-06 15:10

Recently I had to install the patch for fixing cross-platform PDB transport bug onto the docker images with Oracle, so these are easy way how to do it:

1. create directory “patches” and create “install_patches.sh”:

#!/bin/bash

unzip  -u ./*.zip
CURDIR=`pwd`

for D in *; do
    if [ -d "${D}" ]; then
        echo =================================================
        echo " *** Processing patch # ${D}... "   # your processing here
        cd "${D}"
        opatch apply -silent
    fi
    cd $CURDIR
done

2. add the following commands into the dockerfile:

USER root

# Copy DB install file
COPY --chown=oracle:dba patches $INSTALL_DIR/patches

# Install DB software binaries
USER oracle
RUN chmod ug+x $INSTALL_DIR/patches/*.sh && \
    sync && \
    cd $INSTALL_DIR/patches && \
    ./install_patches.sh

3. put downloaded patches into the “patches” directory and build image.

For example, dockerfile for 18.3:

FROM oracle/database:18.3.0-ee

MAINTAINER Sayan Malakshinov <sayan@orasql.org>

USER root

# Copy patches:
COPY --chown=oracle:dba patches $INSTALL_DIR/patches

# Install patches:
USER oracle
RUN chmod ug+x $INSTALL_DIR/patches/*.sh && \
    sync && \
    cd $INSTALL_DIR/patches && \
    ./install_patches.sh

ps. I’ve also create the request for that in the official Docker github: https://github.com/oracle/docker-images/issues/1070

Categories: Development

Adding Calculated Fields to Your Visual Builder UI

Shay Shmeltzer - Tue, 2018-12-04 17:16

This is a quick blog to show two techniques for adding calculated fields to an Oracle Visual Builder application.

Both techniques do the calculation on the client side (in the browser). Keep in mind that you might want to consider doing the calculation on the back-end of your application and get the calculated value delivered directly to your client - in some cases this results in better performance. But sometimes you don't have access to modify the backend, or you can't do calculations there, so here we go:

1. For simple calculation you can just use the value property of a field to do the calculation for you.

For example if you need to know the yearly salary you can take the value in a field and just add *12 to it.

You can also use this to calculate values from multiple fields for example [[$current.data.firstName + " " +$current.data.lastName]] - will get you a field with the full name.

2. For more complex calculation you might need to write some logic to arrive at your calculated value, for example if you have multiple if/then conditions. To do that you can create a client side JavaScript function in your page's JS section. Then you refer to the function from your UI component's value attribute using something like {{$functions.myFunc($current.data.salary)}}

As you'll see in the demo, if you switch to the code view of your application the code editor in Oracle VB will give you code insight into the functions you have for your page, helping you eliminate coding errors.

Categories: Development

The Way

Greg Pavlik - Thu, 2018-11-22 13:48
Walking the Camino one meets people from all walks of life - hailing from virtually everywhere, from virtually every continent and creed. All on a path to their destination. Isn't that life itself?








Filtering Data Providers with Compound Conditions in Visual Builder

Shay Shmeltzer - Mon, 2018-11-19 13:53

I posted in the past a basic introduction to filtering lists in Visual Builder - showing how to use the filterCriterion to filter the records shown in a ServiceDataProvider. Since I recorded this video, a few things changed, and I also saw several customers asking how can they use more complex conditions that involve more than one filter.

In the video below I show how to define a basic filter with the latest versions (note that in VB 18.4.1 you no longer need to surround value with quotes ""), and then I show how to create a more complex condition that involves two filter criteria and set them to work with either an or or an and operator.

When you are using business components in Visual Builder, the filterCriterion is translated into a "q" parameter that is passed to the GET method (more about this q query parameter here). If you find that you are not getting the records you are expecting, check out the browser's Network tab to see what query parameter was passed in your call to the REST service (intro to this debugging technique here).

As you'll see the filterCriterion contains an array of "criteria" so you can specify several of them. In the video I'm using an approach that Brian Fry showed me that gives you a more declarative way to populate the array dragging and dropping multiple "criteria type" variables into the same array.

Note however that the important thing is what is actually being populated in the json file that defines the action. You should go into this view and verify that you have the right structure there. You can also directly manipulate that source to achieve the filter you need.

As you'll see in the video there are some cases where the design time for this filterCriterion adds an entry into the JSON that might not match what you want (we are tracking this issue). So as mentioned - if things don't work as expected direct manipulation of the JSON might be required. 

Categories: Development

Oracle JET UI on Top of Oracle ADF With Visual Builder

Shay Shmeltzer - Thu, 2018-11-15 13:22

At Oracle OpenWorld this year I did a session about the future of Oracle ADF, and one of the demos I did there was showing the powerful combination of Oracle ADF backend with a new Oracle JET UI layer and how Oracle Visual Builder makes this integration very simple.

While we have many happy Oracle ADF customers, we do hear from some of them about new UI requirements that might justify thinking about adopting a new UI architecture for some modules. These type of requirements align with an industry trend towards adopting a more client centric UI architecture that leverages the power of JavaScript on the client. While ADF (which is more of a server centric architecture) does let you leverage JavaScript on the client and provides hook points for that in ADF Faces, some customers prefer a more "puristic" approach for new user interfaces that they are planning to build. Oracle's solution for such a UI architecture is based on Oracle JET - an open source set of libraries we developed and share with the community at http://oraclejet.org.

Oracle Visual Builder provides developers with a simpler approach to building Oracle JET based UIs - for both web and on-device mobile applications. Focusing on a visual UI design approach it drastically reduce the amount of manual coding you need to do to create JET based UIs. 

UIs that you build in Visual Builder connect at the back to REST services, and this is where you can leverage Oracle ADF. In version 12 of JDeveloper we introduced the ability to publish ADF Business Components as REST services through a simple wizard. Note that out-of-the-box you get a very powerful set of services that support things like query by example, pagination, sorting and more. If you haven't explored this functionality already, check out the videos showing how to do it here, and this video covering cloud hosting these services.

Once you have this ADF based REST services layer - you'll be glad to hear that in Visual Builder we have specific support to simplify consuming these REST services. Specifically - we understand the meta-data descriptions that these REST services provide and then are able to create services and endpoints mapping for you.

ADF Describe Dialog in Service Connection

You leverage our "Service from specification" dialog to add your ADF services to your Visual Builder app - and from that point on, it's quite simple to build new JET UIs accessing the data.

In the video below I show how simple it is to build a JET-based on-device mobile app that leverage a set of REST services that were created from Oracle JDeveloper 12. Check it out:

Categories: Development

Conceptions of Fudo Myoo in Esoteric Buddhism

Greg Pavlik - Mon, 2018-11-12 17:14
Admittedly, this is an esoteric topic altogether - my own interest in understanding Fudo Myoo in Mahayana Buddhism have largely stemmed from an interest in Japanese art in the Edo wood block tradition - but I thought a rather interesting exploration of esoteric Buddhism and by implication currents of Japanese culture.

https://tricycle.org/magazine/evil-in-esoteric-japanese-buddhism/

Pages

Subscribe to Oracle FAQ aggregator - Development