[WIP][SPARK-3212][SQL] Use logical plan matching instead of temporary tables for table caching

_Also addresses: SPARK-1379 and SPARK-3641_

This PR introduces a new trait, `CacheManger`, which replaces the previous temporary table based caching system.  Instead of creating a temporary table that shadows an existing table with and equivalent cached representation, the cached manager maintains a separate list of logical plans and their cached data.  After optimization, this list is searched for any matching plan fragments.  When a matching plan fragment is found it is replaced with the cached data.

There are several advantages to this approach:
 - Calling .cache() on a SchemaRDD now works as you would expect, and uses the more efficient columnar representation.
 - Its now possible to provide a list of temporary tables, without having to decide if a given table is actually just a  cached persistent table. (To be done in a follow-up PR)
 - In some cases it is possible that cached data will be used, even if a cached table was not explicitly requested.  This is because we now look at the logical structure instead of the table name.

TODO:
 - [ ] Finish cleanup of caching specific pattern matching code
 - [ ] More test cases for `sameResult` function
 - [ ] Clean up warning when running hive tests.
Moving class and size comparison first would be more efficient:

```scala
    plan.getClass == this.getClass &&
    plan.children.size == children.size && {
      logDebug(s"[${cleanArgs.mkString(", ")}] == [${plan.cleanArgs.mkString(", ")}]")
      cleanArgs == plan.cleanArgs
    } &&
    (plan.children, children).zipped.forall(_ sameResult _)
```